From e560415fde967483573d42f628e52501768584e0 Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Sun, 10 Jul 2022 19:45:26 +0200 Subject: [PATCH 001/178] :rainbow: --- .gitignore | 4 + .vscode/settings.json | 15 + Cargo.toml | 22 + examples/rpi-pico-w/.cargo/config.toml | 8 + examples/rpi-pico-w/Cargo.toml | 59 ++ examples/rpi-pico-w/build.rs | 36 + examples/rpi-pico-w/memory.x | 5 + examples/rpi-pico-w/src/main.rs | 39 ++ rustfmt.toml | 3 + src/fmt.rs | 228 +++++++ src/lib.rs | 879 +++++++++++++++++++++++++ 11 files changed, 1298 insertions(+) create mode 100644 .gitignore create mode 100644 .vscode/settings.json create mode 100644 Cargo.toml create mode 100644 examples/rpi-pico-w/.cargo/config.toml create mode 100644 examples/rpi-pico-w/Cargo.toml create mode 100644 examples/rpi-pico-w/build.rs create mode 100644 examples/rpi-pico-w/memory.x create mode 100644 examples/rpi-pico-w/src/main.rs create mode 100644 rustfmt.toml create mode 100644 src/fmt.rs create mode 100644 src/lib.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..ee8e6b4c --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +Cargo.lock +target/ +*.bin +notes.txt diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..cc926d04 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,15 @@ +{ + "editor.formatOnSave": true, + "rust-analyzer.cargo.buildScripts.enable": true, + "rust-analyzer.cargo.noDefaultFeatures": true, + "rust-analyzer.cargo.target": "thumbv6m-none-eabi", + "rust-analyzer.checkOnSave.allTargets": false, + "rust-analyzer.checkOnSave.noDefaultFeatures": true, + "rust-analyzer.imports.granularity.enforce": true, + "rust-analyzer.imports.granularity.group": "module", + "rust-analyzer.procMacro.attributes.enable": false, + "rust-analyzer.procMacro.enable": true, + "rust-analyzer.linkedProjects": [ + "examples/rpi-pico-w/Cargo.toml", + ], +} \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 00000000..d1672146 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "cyw43" +version = "0.1.0" +edition = "2021" + +[features] +defmt = ["dep:defmt", "embassy-rp/defmt", "embassy/defmt"] +log = ["dep:log"] +[dependencies] +embassy = { version = "0.1.0" } +embassy-rp = { version = "0.1.0", features = ["unstable-traits", "nightly", "unstable-pac"] } +atomic-polyfill = "0.1.5" + +defmt = { version = "0.3", optional = true } +log = { version = "0.4.17", optional = true } + +cortex-m = "0.7.3" +cortex-m-rt = "0.7.0" +futures = { version = "0.3.17", default-features = false, features = ["async-await", "cfg-target-has-atomic", "unstable"] } + +embedded-hal-1 = { package = "embedded-hal", version = "1.0.0-alpha.8" } +embedded-hal-async = { version = "0.1.0-alpha.1" } diff --git a/examples/rpi-pico-w/.cargo/config.toml b/examples/rpi-pico-w/.cargo/config.toml new file mode 100644 index 00000000..18bd4dfe --- /dev/null +++ b/examples/rpi-pico-w/.cargo/config.toml @@ -0,0 +1,8 @@ +[target.'cfg(all(target_arch = "arm", target_os = "none"))'] +runner = "probe-run --chip RP2040" + +[build] +target = "thumbv6m-none-eabi" + +[env] +DEFMT_LOG = "trace" diff --git a/examples/rpi-pico-w/Cargo.toml b/examples/rpi-pico-w/Cargo.toml new file mode 100644 index 00000000..8dbcb20d --- /dev/null +++ b/examples/rpi-pico-w/Cargo.toml @@ -0,0 +1,59 @@ +[package] +name = "cyw43-example-rpi-pico-w" +version = "0.1.0" +edition = "2021" + + +[dependencies] +cyw43 = { path = "../../", features = ["defmt"]} +embassy = { version = "0.1.0", features = ["defmt", "defmt-timestamp-uptime"] } +embassy-rp = { version = "0.1.0", features = ["defmt", "unstable-traits", "nightly", "unstable-pac"] } +atomic-polyfill = "0.1.5" + +defmt = "0.3" +defmt-rtt = "0.3" +panic-probe = { version = "0.3", features = ["print-defmt"] } + +cortex-m = "0.7.3" +cortex-m-rt = "0.7.0" +futures = { version = "0.3.17", default-features = false, features = ["async-await", "cfg-target-has-atomic", "unstable"] } + +embedded-hal-1 = { package = "embedded-hal", version = "1.0.0-alpha.8" } +embedded-hal-async = { version = "0.1.0-alpha.1" } + + +[patch.crates-io] +embassy = { git = "https://github.com/embassy-rs/embassy", rev = "5f43c1d37e9db847c7861fe0bd821db62edba9f6" } +embassy-rp = { git = "https://github.com/embassy-rs/embassy", rev = "5f43c1d37e9db847c7861fe0bd821db62edba9f6" } +#embassy = { path = "/home/dirbaio/embassy/embassy/embassy" } +#embassy-rp = { path = "/home/dirbaio/embassy/embassy/embassy-rp" } + +[profile.dev] +debug = 2 +debug-assertions = true +opt-level = 1 +overflow-checks = true + +[profile.release] +codegen-units = 1 +debug = 2 +debug-assertions = false +incremental = false +lto = 'fat' +opt-level = 'z' +overflow-checks = false + +# do not optimize proc-macro crates = faster builds from scratch +[profile.dev.build-override] +codegen-units = 8 +debug = false +debug-assertions = false +opt-level = 0 +overflow-checks = false + +[profile.release.build-override] +codegen-units = 8 +debug = false +debug-assertions = false +opt-level = 0 +overflow-checks = false diff --git a/examples/rpi-pico-w/build.rs b/examples/rpi-pico-w/build.rs new file mode 100644 index 00000000..3f915f93 --- /dev/null +++ b/examples/rpi-pico-w/build.rs @@ -0,0 +1,36 @@ +//! This build script copies the `memory.x` file from the crate root into +//! a directory where the linker can always find it at build time. +//! For many projects this is optional, as the linker always searches the +//! project root directory -- wherever `Cargo.toml` is. However, if you +//! are using a workspace or have a more complicated build setup, this +//! build script becomes required. Additionally, by requesting that +//! Cargo re-run the build script whenever `memory.x` is changed, +//! updating `memory.x` ensures a rebuild of the application with the +//! new memory settings. + +use std::env; +use std::fs::File; +use std::io::Write; +use std::path::PathBuf; + +fn main() { + // Put `memory.x` in our output directory and ensure it's + // on the linker search path. + let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap()); + File::create(out.join("memory.x")) + .unwrap() + .write_all(include_bytes!("memory.x")) + .unwrap(); + println!("cargo:rustc-link-search={}", out.display()); + + // By default, Cargo will re-run a build script whenever + // any file in the project changes. By specifying `memory.x` + // here, we ensure the build script is only re-run when + // `memory.x` is changed. + println!("cargo:rerun-if-changed=memory.x"); + + println!("cargo:rustc-link-arg-bins=--nmagic"); + println!("cargo:rustc-link-arg-bins=-Tlink.x"); + println!("cargo:rustc-link-arg-bins=-Tlink-rp.x"); + println!("cargo:rustc-link-arg-bins=-Tdefmt.x"); +} diff --git a/examples/rpi-pico-w/memory.x b/examples/rpi-pico-w/memory.x new file mode 100644 index 00000000..eb8c1731 --- /dev/null +++ b/examples/rpi-pico-w/memory.x @@ -0,0 +1,5 @@ +MEMORY { + BOOT2 : ORIGIN = 0x10000000, LENGTH = 0x100 + FLASH : ORIGIN = 0x10000100, LENGTH = 1024K - 0x100 + RAM : ORIGIN = 0x20000000, LENGTH = 256K +} \ No newline at end of file diff --git a/examples/rpi-pico-w/src/main.rs b/examples/rpi-pico-w/src/main.rs new file mode 100644 index 00000000..de8c5a2b --- /dev/null +++ b/examples/rpi-pico-w/src/main.rs @@ -0,0 +1,39 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait, concat_bytes)] + +use core::slice; + +use defmt::{assert, assert_eq, panic, *}; +use embassy::executor::Spawner; +use embassy_rp::gpio::{Flex, Level, Output, Pin}; +use embassy_rp::Peripherals; +use {defmt_rtt as _, panic_probe as _}; + + +macro_rules! forever { + ($val:expr) => {{ + type T = impl Sized; + static FOREVER: Forever = Forever::new(); + FOREVER.put_with(move || $val) + }}; +} + +#[embassy::main] +async fn main(_spawner: Spawner, p: Peripherals) { + info!("Hello World!"); + + let (pwr, cs, clk, dio) = (p.PIN_23, p.PIN_25, p.PIN_29, p.PIN_24); + //let (pwr, cs, clk, dio) = (p.PIN_23, p.PIN_0, p.PIN_1, p.PIN_2); + + let mut driver = cyw43::Driver::new( + Output::new(pwr, Level::Low), + Output::new(cs, Level::High), + Output::new(clk, Level::Low), + Flex::new(dio), + ); + + driver.init().await; + + loop {} +} diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 00000000..3639f438 --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1,3 @@ +group_imports = "StdExternalCrate" +imports_granularity = "Module" +max_width=120 \ No newline at end of file diff --git a/src/fmt.rs b/src/fmt.rs new file mode 100644 index 00000000..f8bb0a03 --- /dev/null +++ b/src/fmt.rs @@ -0,0 +1,228 @@ +#![macro_use] +#![allow(unused_macros)] + +#[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)*); + } + }; +} + +macro_rules! unreachable { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::unreachable!($($x)*); + #[cfg(feature = "defmt")] + ::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); + } + } + } +} + +#[cfg(feature = "defmt-timestamp-uptime")] +defmt::timestamp! {"{=u64:us}", crate::time::Instant::now().as_micros() } + +#[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 + } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 00000000..5caf1926 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,879 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait, concat_bytes)] + +// This mod MUST go first, so that the others see its macros. +pub(crate) mod fmt; + +use core::slice; + +use embassy::time::{block_for, Duration, Timer}; +use embassy::util::yield_now; +use embassy_rp::gpio::{Flex, Output, Pin}; + +fn swap16(x: u32) -> u32 { + (x & 0xFF00FF00) >> 8 | (x & 0x00FF00FF) << 8 +} + +fn cmd_word(write: bool, incr: bool, func: u32, addr: u32, len: u32) -> u32 { + (write as u32) << 31 | (incr as u32) << 30 | (func & 0b11) << 28 | (addr & 0x1FFFF) << 11 | (len & 0x7FF) +} + +const FUNC_BUS: u32 = 0; +const FUNC_BACKPLANE: u32 = 1; +const FUNC_WLAN: u32 = 2; +const FUNC_BT: u32 = 3; + +const REG_BUS_CTRL: u32 = 0x0; +const REG_BUS_INTERRUPT: u32 = 0x04; // 16 bits - Interrupt status +const REG_BUS_INTERRUPT_ENABLE: u32 = 0x06; // 16 bits - Interrupt mask +const REG_BUS_STATUS: u32 = 0x8; +const REG_BUS_FEEDBEAD: u32 = 0x14; +const REG_BUS_TEST: u32 = 0x18; +const REG_BUS_RESP_DELAY: u32 = 0x1c; + +// SPI_STATUS_REGISTER bits +const STATUS_DATA_NOT_AVAILABLE: u32 = 0x00000001; +const STATUS_UNDERFLOW: u32 = 0x00000002; +const STATUS_OVERFLOW: u32 = 0x00000004; +const STATUS_F2_INTR: u32 = 0x00000008; +const STATUS_F3_INTR: u32 = 0x00000010; +const STATUS_F2_RX_READY: u32 = 0x00000020; +const STATUS_F3_RX_READY: u32 = 0x00000040; +const STATUS_HOST_CMD_DATA_ERR: u32 = 0x00000080; +const STATUS_F2_PKT_AVAILABLE: u32 = 0x00000100; +const STATUS_F2_PKT_LEN_MASK: u32 = 0x000FFE00; +const STATUS_F2_PKT_LEN_SHIFT: u32 = 9; +const STATUS_F3_PKT_AVAILABLE: u32 = 0x00100000; +const STATUS_F3_PKT_LEN_MASK: u32 = 0xFFE00000; +const STATUS_F3_PKT_LEN_SHIFT: u32 = 21; + +const REG_BACKPLANE_GPIO_SELECT: u32 = 0x10005; +const REG_BACKPLANE_GPIO_OUTPUT: u32 = 0x10006; +const REG_BACKPLANE_GPIO_ENABLE: u32 = 0x10007; +const REG_BACKPLANE_FUNCTION2_WATERMARK: u32 = 0x10008; +const REG_BACKPLANE_DEVICE_CONTROL: u32 = 0x10009; +const REG_BACKPLANE_BACKPLANE_ADDRESS_LOW: u32 = 0x1000A; +const REG_BACKPLANE_BACKPLANE_ADDRESS_MID: u32 = 0x1000B; +const REG_BACKPLANE_BACKPLANE_ADDRESS_HIGH: u32 = 0x1000C; +const REG_BACKPLANE_FRAME_CONTROL: u32 = 0x1000D; +const REG_BACKPLANE_CHIP_CLOCK_CSR: u32 = 0x1000E; +const REG_BACKPLANE_PULL_UP: u32 = 0x1000F; +const REG_BACKPLANE_READ_FRAME_BC_LOW: u32 = 0x1001B; +const REG_BACKPLANE_READ_FRAME_BC_HIGH: u32 = 0x1001C; +const REG_BACKPLANE_WAKEUP_CTRL: u32 = 0x1001E; +const REG_BACKPLANE_SLEEP_CSR: u32 = 0x1001F; + +const BACKPLANE_WINDOW_SIZE: usize = 0x8000; +const BACKPLANE_ADDRESS_MASK: u32 = 0x7FFF; +const BACKPLANE_ADDRESS_32BIT_FLAG: u32 = 0x08000; +const BACKPLANE_MAX_TRANSFER_SIZE: usize = 64; + +const AI_IOCTRL_OFFSET: u32 = 0x408; +const AI_IOCTRL_BIT_FGC: u8 = 0x0002; +const AI_IOCTRL_BIT_CLOCK_EN: u8 = 0x0001; +const AI_IOCTRL_BIT_CPUHALT: u8 = 0x0020; + +const AI_RESETCTRL_OFFSET: u32 = 0x800; +const AI_RESETCTRL_BIT_RESET: u8 = 1; + +const AI_RESETSTATUS_OFFSET: u32 = 0x804; + +const TEST_PATTERN: u32 = 0x12345678; +const FEEDBEAD: u32 = 0xFEEDBEAD; + +// SPI_INTERRUPT_REGISTER and SPI_INTERRUPT_ENABLE_REGISTER Bits +const IRQ_DATA_UNAVAILABLE: u16 = 0x0001; // Requested data not available; Clear by writing a "1" +const IRQ_F2_F3_FIFO_RD_UNDERFLOW: u16 = 0x0002; +const IRQ_F2_F3_FIFO_WR_OVERFLOW: u16 = 0x0004; +const IRQ_COMMAND_ERROR: u16 = 0x0008; // Cleared by writing 1 +const IRQ_DATA_ERROR: u16 = 0x0010; // Cleared by writing 1 +const IRQ_F2_PACKET_AVAILABLE: u16 = 0x0020; +const IRQ_F3_PACKET_AVAILABLE: u16 = 0x0040; +const IRQ_F1_OVERFLOW: u16 = 0x0080; // Due to last write. Bkplane has pending write requests +const IRQ_MISC_INTR0: u16 = 0x0100; +const IRQ_MISC_INTR1: u16 = 0x0200; +const IRQ_MISC_INTR2: u16 = 0x0400; +const IRQ_MISC_INTR3: u16 = 0x0800; +const IRQ_MISC_INTR4: u16 = 0x1000; +const IRQ_F1_INTR: u16 = 0x2000; +const IRQ_F2_INTR: u16 = 0x4000; +const IRQ_F3_INTR: u16 = 0x8000; + +#[derive(Clone, Copy, PartialEq, Eq)] +enum Core { + WLAN = 0, + SOCSRAM = 1, + SDIOD = 2, +} + +impl Core { + fn base_addr(&self) -> u32 { + match self { + Self::WLAN => CHIP.arm_core_base_address, + Self::SOCSRAM => CHIP.socsram_wrapper_base_address, + Self::SDIOD => CHIP.sdiod_core_base_address, + } + } +} + +struct Chip { + arm_core_base_address: u32, + socsram_base_address: u32, + socsram_wrapper_base_address: u32, + sdiod_core_base_address: u32, + pmu_base_address: u32, + chip_ram_size: u32, + atcm_ram_base_address: u32, + socram_srmem_size: u32, + chanspec_band_mask: u32, + chanspec_band_2g: u32, + chanspec_band_5g: u32, + chanspec_band_shift: u32, + chanspec_bw_10: u32, + chanspec_bw_20: u32, + chanspec_bw_40: u32, + chanspec_bw_mask: u32, + chanspec_bw_shift: u32, + chanspec_ctl_sb_lower: u32, + chanspec_ctl_sb_upper: u32, + chanspec_ctl_sb_none: u32, + chanspec_ctl_sb_mask: u32, +} + +const WRAPPER_REGISTER_OFFSET: u32 = 0x100000; + +// Data for CYW43439 +const CHIP: Chip = Chip { + arm_core_base_address: 0x18003000 + WRAPPER_REGISTER_OFFSET, + socsram_base_address: 0x18004000, + socsram_wrapper_base_address: 0x18004000 + WRAPPER_REGISTER_OFFSET, + sdiod_core_base_address: 0x18002000, + pmu_base_address: 0x18000000, + chip_ram_size: 512 * 1024, + atcm_ram_base_address: 0, + socram_srmem_size: 64 * 1024, + chanspec_band_mask: 0xc000, + chanspec_band_2g: 0x0000, + chanspec_band_5g: 0xc000, + chanspec_band_shift: 14, + chanspec_bw_10: 0x0800, + chanspec_bw_20: 0x1000, + chanspec_bw_40: 0x1800, + chanspec_bw_mask: 0x3800, + chanspec_bw_shift: 11, + chanspec_ctl_sb_lower: 0x0000, + chanspec_ctl_sb_upper: 0x0100, + chanspec_ctl_sb_none: 0x0000, + chanspec_ctl_sb_mask: 0x0700, +}; + +#[derive(Clone, Copy)] +#[repr(C)] +struct SdpcmHeader { + len: u16, + len_inv: u16, + /// Rx/Tx sequence number + sequence: u8, + /// 4 MSB Channel number, 4 LSB arbitrary flag + channel_and_flags: u8, + /// Length of next data frame, reserved for Tx + next_length: u8, + /// Data offset + header_length: u8, + /// Flow control bits, reserved for Tx + wireless_flow_control: u8, + /// Maximum Sequence number allowed by firmware for Tx + bus_data_credit: u8, + /// Reserved + reserved: [u8; 2], +} + +#[derive(Clone, Copy)] +#[repr(C)] +struct CdcHeader { + cmd: u32, + out_len: u16, + in_len: u16, + flags: u16, + id: u16, + status: u32, +} + +#[derive(Clone, Copy)] +#[repr(C)] +struct BdcHeader { + flags: u8, + /// 802.1d Priority (low 3 bits) + priority: u8, + flags2: u8, + /// Offset from end of BDC header to packet data, in 4-uint8_t words. Leaves room for optional headers. + data_offset: u8, +} + +macro_rules! impl_bytes { + ($t:ident) => { + impl $t { + const SIZE: usize = core::mem::size_of::(); + + pub fn to_bytes(&self) -> [u8; Self::SIZE] { + unsafe { core::mem::transmute(*self) } + } + + pub fn from_bytes(bytes: &[u8; Self::SIZE]) -> Self { + unsafe { core::mem::transmute(*bytes) } + } + } + }; +} +impl_bytes!(SdpcmHeader); +impl_bytes!(CdcHeader); +impl_bytes!(BdcHeader); + +pub struct Driver<'a, PWR: Pin, CS: Pin, CLK: Pin, DIO: Pin> { + pwr: Output<'a, PWR>, + + /// SPI chip-select. + cs: Output<'a, CS>, + + /// SPI clock + clk: Output<'a, CLK>, + + /// 4 signals, all in one!! + /// - SPI MISO + /// - SPI MOSI + /// - IRQ + /// - strap to set to gSPI mode on boot. + dio: Flex<'a, DIO>, + + backplane_window: u32, +} + +impl<'a, PWR: Pin, CS: Pin, CLK: Pin, DIO: Pin> Driver<'a, PWR, CS, CLK, DIO> { + pub fn new(pwr: Output<'a, PWR>, cs: Output<'a, CS>, clk: Output<'a, CLK>, dio: Flex<'a, DIO>) -> Self { + Self { + pwr, + cs, + clk, + dio, + backplane_window: 0xAAAA_AAAA, + } + } + + pub async fn init(&mut self) { + // Set strap to select gSPI mode. + self.dio.set_as_output(); + self.dio.set_low(); + + // Reset + self.pwr.set_low(); + Timer::after(Duration::from_millis(20)).await; + self.pwr.set_high(); + Timer::after(Duration::from_millis(250)).await; + + info!("waiting for ping..."); + while self.read32_swapped(REG_BUS_FEEDBEAD) != FEEDBEAD {} + info!("ping ok"); + + self.write32_swapped(0x18, TEST_PATTERN); + let val = self.read32_swapped(REG_BUS_TEST); + assert_eq!(val, TEST_PATTERN); + + // 32bit, big endian. + self.write32_swapped(REG_BUS_CTRL, 0x00010033); + + let val = self.read32(FUNC_BUS, REG_BUS_FEEDBEAD); + assert_eq!(val, FEEDBEAD); + let val = self.read32(FUNC_BUS, REG_BUS_TEST); + assert_eq!(val, TEST_PATTERN); + + // No response delay in any of the funcs. + // seems to break backplane??? eat the 4-byte delay instead, that's what the vendor drivers do... + //self.write32(FUNC_BUS, REG_BUS_RESP_DELAY, 0); + + // Init ALP (no idea what that stands for) clock + self.write8(FUNC_BACKPLANE, REG_BACKPLANE_CHIP_CLOCK_CSR, 0x08); + info!("waiting for clock..."); + while self.read8(FUNC_BACKPLANE, REG_BACKPLANE_CHIP_CLOCK_CSR) & 0x40 == 0 {} + info!("clock ok"); + + let chip_id = self.bp_read16(0x1800_0000); + info!("chip ID: {}", chip_id); + + // Upload firmware. + self.core_disable(Core::WLAN); + self.core_reset(Core::SOCSRAM); + self.bp_write32(CHIP.socsram_base_address + 0x10, 3); + self.bp_write32(CHIP.socsram_base_address + 0x44, 0); + + // I'm flashing the firmwares independently at hardcoded addresses, instead of baking them + // into the program with `include_bytes!` or similar, so that flashing the program stays fast. + // + // Flash them like this, also don't forget to update the lengths below if you change them!. + // + // probe-rs-cli download 43439A0.bin --format bin --chip RP2040 --base-address 0x10100000 + // probe-rs-cli download 43439A0.clm_blob --format bin --chip RP2040 --base-address 0x10140000 + let fw = unsafe { slice::from_raw_parts(0x10100000 as *const u8, 224190) }; + let clm = unsafe { slice::from_raw_parts(0x10140000 as *const u8, 4752) }; + + let ram_addr = CHIP.atcm_ram_base_address; + + info!("loading fw"); + self.bp_write(ram_addr, fw); + + info!("verifying fw"); + let mut buf = [0; 1024]; + for (i, chunk) in fw.chunks(1024).enumerate() { + let buf = &mut buf[..chunk.len()]; + self.bp_read(ram_addr + i as u32 * 1024, buf); + assert_eq!(chunk, buf); + } + + info!("loading nvram"); + // Round up to 4 bytes. + let nvram_len = (NVRAM.len() + 3) / 4 * 4; + self.bp_write(ram_addr + CHIP.chip_ram_size - 4 - nvram_len as u32, NVRAM); + + let nvram_len_words = nvram_len as u32 / 4; + let nvram_len_magic = (!nvram_len_words << 16) | nvram_len_words; + self.bp_write32(ram_addr + CHIP.chip_ram_size - 4, nvram_len_magic); + + // Start core! + info!("starting up core..."); + self.core_reset(Core::WLAN); + assert!(self.core_is_up(Core::WLAN)); + + while self.read8(FUNC_BACKPLANE, REG_BACKPLANE_CHIP_CLOCK_CSR) & 0x80 == 0 {} + + // "Set up the interrupt mask and enable interrupts" + self.bp_write32(CHIP.sdiod_core_base_address + 0x24, 0xF0); + + // "Lower F2 Watermark to avoid DMA Hang in F2 when SD Clock is stopped." + // Sounds scary... + self.write8(FUNC_BACKPLANE, REG_BACKPLANE_FUNCTION2_WATERMARK, 32); + + // wait for wifi startup + info!("waiting for wifi init..."); + while self.read32(FUNC_BUS, REG_BUS_STATUS) & STATUS_F2_RX_READY == 0 {} + + // Some random configs related to sleep. + // I think they're not needed if we don't want sleep...??? + /* + let mut val = self.read8(FUNC_BACKPLANE, REG_BACKPLANE_WAKEUP_CTRL); + val |= 0x02; // WAKE_TILL_HT_AVAIL + self.write8(FUNC_BACKPLANE, REG_BACKPLANE_WAKEUP_CTRL, val); + self.write8(FUNC_BUS, 0xF0, 0x08); // SDIOD_CCCR_BRCM_CARDCAP.CMD_NODEC = 1 + self.write8(FUNC_BACKPLANE, REG_BACKPLANE_CHIP_CLOCK_CSR, 0x02); // SBSDIO_FORCE_HT + + let mut val = self.read8(FUNC_BACKPLANE, REG_BACKPLANE_SLEEP_CSR); + val |= 0x01; // SBSDIO_SLPCSR_KEEP_SDIO_ON + self.write8(FUNC_BACKPLANE, REG_BACKPLANE_SLEEP_CSR, val); + + // clear pulls + self.write8(FUNC_BACKPLANE, REG_BACKPLANE_PULL_UP, 0); + let _ = self.read8(FUNC_BACKPLANE, REG_BACKPLANE_PULL_UP); + */ + + let mut buf = [0; 8 + 12 + 1024]; + buf[0..8].copy_from_slice(b"clmload\x00"); + buf[8..20].copy_from_slice(b"\x02\x10\x02\x00\x00\x04\x00\x00\x00\x00\x00\x00"); + buf[20..].copy_from_slice(&clm[..1024]); + self.send_ioctl(2, 263, 0, &buf); + + info!("init done "); + + let mut old_irq = 0; + let mut buf = [0; 2048]; + loop { + let irq = self.read16(FUNC_BUS, REG_BUS_INTERRUPT); + if irq != old_irq { + info!("irq: {:04x}", irq); + } + old_irq = irq; + + if irq & IRQ_F2_PACKET_AVAILABLE != 0 { + let mut status = 0xFFFF_FFFF; + while status == 0xFFFF_FFFF { + status = self.read32(FUNC_BUS, REG_BUS_STATUS); + } + + if status & STATUS_F2_PKT_AVAILABLE != 0 { + let len = (status & STATUS_F2_PKT_LEN_MASK) >> STATUS_F2_PKT_LEN_SHIFT; + info!("got len {}", len); + + let cmd = cmd_word(false, true, FUNC_WLAN, 0, len); + + self.cs.set_low(); + self.spi_write(&cmd.to_le_bytes()); + self.spi_read(&mut buf[..len as usize]); + // pad to 32bit + let mut junk = [0; 4]; + if len % 4 != 0 { + self.spi_read(&mut junk[..(4 - len as usize % 4)]); + } + self.cs.set_high(); + + info!("rxd packet {:02x}", &buf[..len as usize]); + + self.rx(&buf[..len as usize]); + } + } + + yield_now().await; + } + } + + fn rx(&mut self, packet: &[u8]) { + if packet.len() < SdpcmHeader::SIZE { + warn!("packet too short, len={}", packet.len()); + return; + } + + let sdpcm_header = SdpcmHeader::from_bytes(packet[..SdpcmHeader::SIZE].try_into().unwrap()); + + if sdpcm_header.len != !sdpcm_header.len_inv { + warn!("len inv mismatch"); + return; + } + if sdpcm_header.len as usize != packet.len() { + // TODO: is this guaranteed?? + warn!("len from header doesn't match len from spi"); + return; + } + + let channel = sdpcm_header.channel_and_flags & 0x0f; + + match channel { + 0 => { + if packet.len() < SdpcmHeader::SIZE + CdcHeader::SIZE { + warn!("control packet too short, len={}", packet.len()); + return; + } + + let cdc_header = + CdcHeader::from_bytes(packet[SdpcmHeader::SIZE..][..CdcHeader::SIZE].try_into().unwrap()); + + // TODO check cdc_header.id matches + // TODO check status + } + _ => {} + } + } + + fn send_ioctl(&mut self, kind: u32, cmd: u32, iface: u32, data: &[u8]) { + let mut buf = [0; 2048]; + + let total_len = SdpcmHeader::SIZE + CdcHeader::SIZE + data.len(); + + let sdpcm_header = SdpcmHeader { + len: total_len as u16, + len_inv: !total_len as u16, + sequence: 0x02, // todo + channel_and_flags: 0, // control channle + next_length: 0, + header_length: SdpcmHeader::SIZE as _, + wireless_flow_control: 0, + bus_data_credit: 0, + reserved: [0, 0], + }; + + let cdc_header = CdcHeader { + cmd: cmd, + out_len: data.len() as _, + in_len: 0, + flags: kind as u16 | (iface as u16) << 12, + id: 1, // todo + status: 0, + }; + + buf[0..SdpcmHeader::SIZE].copy_from_slice(&sdpcm_header.to_bytes()); + buf[SdpcmHeader::SIZE..][..CdcHeader::SIZE].copy_from_slice(&cdc_header.to_bytes()); + buf[SdpcmHeader::SIZE + CdcHeader::SIZE..][..data.len()].copy_from_slice(data); + + info!("txing {:02x}", &buf[..total_len]); + + let cmd = cmd_word(true, true, FUNC_WLAN, 0, total_len as _); + self.cs.set_low(); + self.spi_write(&cmd.to_le_bytes()); + self.spi_write(&buf[..total_len]); + self.cs.set_high(); + } + + fn core_disable(&mut self, core: Core) { + let base = core.base_addr(); + + // Dummy read? + let _ = self.bp_read8(base + AI_RESETCTRL_OFFSET); + + // Check it isn't already reset + let r = self.bp_read8(base + AI_RESETCTRL_OFFSET); + if r & AI_RESETCTRL_BIT_RESET != 0 { + return; + } + + self.bp_write8(base + AI_IOCTRL_OFFSET, 0); + let _ = self.bp_read8(base + AI_IOCTRL_OFFSET); + + block_for(Duration::from_millis(1)); + + self.bp_write8(base + AI_RESETCTRL_OFFSET, AI_RESETCTRL_BIT_RESET); + let _ = self.bp_read8(base + AI_RESETCTRL_OFFSET); + } + + fn core_reset(&mut self, core: Core) { + self.core_disable(core); + + let base = core.base_addr(); + self.bp_write8(base + AI_IOCTRL_OFFSET, AI_IOCTRL_BIT_FGC | AI_IOCTRL_BIT_CLOCK_EN); + let _ = self.bp_read8(base + AI_IOCTRL_OFFSET); + + self.bp_write8(base + AI_RESETCTRL_OFFSET, 0); + + block_for(Duration::from_millis(1)); + + self.bp_write8(base + AI_IOCTRL_OFFSET, AI_IOCTRL_BIT_CLOCK_EN); + let _ = self.bp_read8(base + AI_IOCTRL_OFFSET); + + block_for(Duration::from_millis(1)); + } + + fn core_is_up(&mut self, core: Core) -> bool { + let base = core.base_addr(); + + let io = self.bp_read8(base + AI_IOCTRL_OFFSET); + if io & (AI_IOCTRL_BIT_FGC | AI_IOCTRL_BIT_CLOCK_EN) != AI_IOCTRL_BIT_CLOCK_EN { + debug!("core_is_up: returning false due to bad ioctrl {:02x}", io); + return false; + } + + let r = self.bp_read8(base + AI_RESETCTRL_OFFSET); + if r & (AI_RESETCTRL_BIT_RESET) != 0 { + debug!("core_is_up: returning false due to bad resetctrl {:02x}", r); + return false; + } + + true + } + + fn bp_read(&mut self, mut addr: u32, mut data: &mut [u8]) { + // It seems the HW force-aligns the addr + // to 2 if data.len() >= 2 + // to 4 if data.len() >= 4 + // To simplify, enforce 4-align for now. + assert!(addr % 4 == 0); + + while !data.is_empty() { + // Ensure transfer doesn't cross a window boundary. + let window_offs = addr & BACKPLANE_ADDRESS_MASK; + let window_remaining = BACKPLANE_WINDOW_SIZE - window_offs as usize; + + let len = data.len().min(BACKPLANE_MAX_TRANSFER_SIZE).min(window_remaining); + + self.backplane_set_window(addr); + + let cmd = cmd_word(false, true, FUNC_BACKPLANE, window_offs, len as u32); + self.cs.set_low(); + self.spi_write(&cmd.to_le_bytes()); + + // 4-byte response delay. + let mut junk = [0; 4]; + self.spi_read(&mut junk); + + // Read data + self.spi_read(&mut data[..len]); + + // pad to 32bit + if len % 4 != 0 { + self.spi_read(&mut junk[..(4 - len % 4)]); + } + self.cs.set_high(); + + // Advance ptr. + addr += len as u32; + data = &mut data[len..]; + } + } + + fn bp_write(&mut self, mut addr: u32, mut data: &[u8]) { + // It seems the HW force-aligns the addr + // to 2 if data.len() >= 2 + // to 4 if data.len() >= 4 + // To simplify, enforce 4-align for now. + assert!(addr % 4 == 0); + + while !data.is_empty() { + // Ensure transfer doesn't cross a window boundary. + let window_offs = addr & BACKPLANE_ADDRESS_MASK; + let window_remaining = BACKPLANE_WINDOW_SIZE - window_offs as usize; + + let len = data.len().min(BACKPLANE_MAX_TRANSFER_SIZE).min(window_remaining); + + self.backplane_set_window(addr); + + let cmd = cmd_word(true, true, FUNC_BACKPLANE, window_offs, len as u32); + self.cs.set_low(); + self.spi_write(&cmd.to_le_bytes()); + self.spi_write(&data[..len]); + // pad to 32bit + if len % 4 != 0 { + let zeros = [0; 4]; + self.spi_write(&zeros[..(4 - len % 4)]); + } + self.cs.set_high(); + + // Advance ptr. + addr += len as u32; + data = &data[len..]; + } + } + + fn bp_read8(&mut self, addr: u32) -> u8 { + self.backplane_readn(addr, 1) as u8 + } + + fn bp_write8(&mut self, addr: u32, val: u8) { + self.backplane_writen(addr, val as u32, 1) + } + + fn bp_read16(&mut self, addr: u32) -> u16 { + self.backplane_readn(addr, 2) as u16 + } + + fn bp_write16(&mut self, addr: u32, val: u16) { + self.backplane_writen(addr, val as u32, 2) + } + + fn bp_read32(&mut self, addr: u32) -> u32 { + self.backplane_readn(addr, 4) + } + + fn bp_write32(&mut self, addr: u32, val: u32) { + self.backplane_writen(addr, val, 4) + } + + fn backplane_readn(&mut self, addr: u32, len: u32) -> u32 { + self.backplane_set_window(addr); + + let mut bus_addr = addr & BACKPLANE_ADDRESS_MASK; + if len == 4 { + bus_addr |= BACKPLANE_ADDRESS_32BIT_FLAG + } + self.readn(FUNC_BACKPLANE, bus_addr, len) + } + + fn backplane_writen(&mut self, addr: u32, val: u32, len: u32) { + self.backplane_set_window(addr); + + let mut bus_addr = addr & BACKPLANE_ADDRESS_MASK; + if len == 4 { + bus_addr |= BACKPLANE_ADDRESS_32BIT_FLAG + } + self.writen(FUNC_BACKPLANE, bus_addr, val, len) + } + + fn backplane_set_window(&mut self, addr: u32) { + let new_window = addr & !BACKPLANE_ADDRESS_MASK; + + if (new_window >> 24) as u8 != (self.backplane_window >> 24) as u8 { + self.write8( + FUNC_BACKPLANE, + REG_BACKPLANE_BACKPLANE_ADDRESS_HIGH, + (new_window >> 24) as u8, + ); + } + if (new_window >> 16) as u8 != (self.backplane_window >> 16) as u8 { + self.write8( + FUNC_BACKPLANE, + REG_BACKPLANE_BACKPLANE_ADDRESS_MID, + (new_window >> 16) as u8, + ); + } + if (new_window >> 8) as u8 != (self.backplane_window >> 8) as u8 { + self.write8( + FUNC_BACKPLANE, + REG_BACKPLANE_BACKPLANE_ADDRESS_LOW, + (new_window >> 8) as u8, + ); + } + self.backplane_window = new_window; + } + + fn read8(&mut self, func: u32, addr: u32) -> u8 { + self.readn(func, addr, 1) as u8 + } + + fn write8(&mut self, func: u32, addr: u32, val: u8) { + self.writen(func, addr, val as u32, 1) + } + + fn read16(&mut self, func: u32, addr: u32) -> u16 { + self.readn(func, addr, 2) as u16 + } + + fn write16(&mut self, func: u32, addr: u32, val: u16) { + self.writen(func, addr, val as u32, 2) + } + + fn read32(&mut self, func: u32, addr: u32) -> u32 { + self.readn(func, addr, 4) + } + + fn write32(&mut self, func: u32, addr: u32, val: u32) { + self.writen(func, addr, val, 4) + } + + fn readn(&mut self, func: u32, addr: u32, len: u32) -> u32 { + let cmd = cmd_word(false, true, func, addr, len); + let mut buf = [0; 4]; + + self.cs.set_low(); + self.spi_write(&cmd.to_le_bytes()); + if func == FUNC_BACKPLANE { + // 4-byte response delay. + self.spi_read(&mut buf); + } + self.spi_read(&mut buf); + self.cs.set_high(); + + u32::from_le_bytes(buf) + } + + fn writen(&mut self, func: u32, addr: u32, val: u32, len: u32) { + let cmd = cmd_word(true, true, func, addr, len); + + self.cs.set_low(); + self.spi_write(&cmd.to_le_bytes()); + self.spi_write(&val.to_le_bytes()); + self.cs.set_high(); + } + + fn read32_swapped(&mut self, addr: u32) -> u32 { + let cmd = cmd_word(false, true, FUNC_BUS, addr, 4); + let mut buf = [0; 4]; + + self.cs.set_low(); + self.spi_write(&swap16(cmd).to_le_bytes()); + self.spi_read(&mut buf); + self.cs.set_high(); + + swap16(u32::from_le_bytes(buf)) + } + + fn write32_swapped(&mut self, addr: u32, val: u32) { + let cmd = cmd_word(true, true, FUNC_BUS, addr, 4); + + self.cs.set_low(); + self.spi_write(&swap16(cmd).to_le_bytes()); + self.spi_write(&swap16(val).to_le_bytes()); + self.cs.set_high(); + } + + fn spi_read(&mut self, words: &mut [u8]) { + self.dio.set_as_input(); + for word in words { + let mut w = 0; + for _ in 0..8 { + w = w << 1; + + // rising edge, sample data + if self.dio.is_high() { + w |= 0x01; + } + self.clk.set_high(); + delay(); + + // falling edge + self.clk.set_low(); + delay(); + } + *word = w + } + self.clk.set_low(); + delay(); + } + + fn spi_write(&mut self, words: &[u8]) { + self.dio.set_as_output(); + for word in words { + let mut word = *word; + for _ in 0..8 { + // falling edge, setup data + self.clk.set_low(); + if word & 0x80 == 0 { + self.dio.set_low(); + } else { + self.dio.set_high(); + } + delay(); + + // rising edge + self.clk.set_high(); + delay(); + + word = word << 1; + } + } + self.clk.set_low(); + delay(); + self.dio.set_as_input(); + } +} + +fn delay() { + //cortex_m::asm::delay(5); +} + +macro_rules! nvram { + ($($s:literal,)*) => { + concat_bytes!($($s, b"\x00",)* b"\x00\x00") + }; +} + +static NVRAM: &'static [u8] = &*nvram!( + b"NVRAMRev=$Rev$", + b"manfid=0x2d0", + b"prodid=0x0727", + b"vendid=0x14e4", + b"devid=0x43e2", + b"boardtype=0x0887", + b"boardrev=0x1100", + b"boardnum=22", + b"macaddr=00:A0:50:86:aa:b6", + b"sromrev=11", + b"boardflags=0x00404001", + b"boardflags3=0x04000000", + b"xtalfreq=26000", + b"nocrc=1", + b"ag0=255", + b"aa2g=1", + b"ccode=ALL", + b"pa0itssit=0x20", + b"extpagain2g=0", + b"pa2ga0=-168,7161,-820", + b"AvVmid_c0=0x0,0xc8", + b"cckpwroffset0=5", + b"maxp2ga0=84", + b"txpwrbckof=6", + b"cckbw202gpo=0", + b"legofdmbw202gpo=0x66111111", + b"mcsbw202gpo=0x77711111", + b"propbw202gpo=0xdd", + b"ofdmdigfilttype=18", + b"ofdmdigfilttypebe=18", + b"papdmode=1", + b"papdvalidtest=1", + b"pacalidx2g=45", + b"papdepsoffset=-30", + b"papdendidx=58", + b"ltecxmux=0", + b"ltecxpadnum=0x0102", + b"ltecxfnsel=0x44", + b"ltecxgcigpio=0x01", + b"il0macaddr=00:90:4c:c5:12:38", + b"wl0id=0x431b", + b"deadman_to=0xffffffff", + b"muxenab=0x1", + b"spurconfig=0x3", + b"glitch_based_crsmin=1", + b"btc_mode=1", +); From 069a57fcf85c725d000e03163716edb4ae3922ca Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Mon, 11 Jul 2022 00:25:35 +0200 Subject: [PATCH 002/178] async ioctls working. --- examples/rpi-pico-w/src/main.rs | 21 +++- rust-toolchain.toml | 8 ++ src/lib.rs | 200 +++++++++++++++++--------------- src/structs.rs | 71 ++++++++++++ 4 files changed, 202 insertions(+), 98 deletions(-) create mode 100644 rust-toolchain.toml create mode 100644 src/structs.rs diff --git a/examples/rpi-pico-w/src/main.rs b/examples/rpi-pico-w/src/main.rs index de8c5a2b..7545dfde 100644 --- a/examples/rpi-pico-w/src/main.rs +++ b/examples/rpi-pico-w/src/main.rs @@ -6,11 +6,12 @@ use core::slice; use defmt::{assert, assert_eq, panic, *}; use embassy::executor::Spawner; +use embassy::util::Forever; use embassy_rp::gpio::{Flex, Level, Output, Pin}; +use embassy_rp::peripherals::{PIN_23, PIN_24, PIN_25, PIN_29}; use embassy_rp::Peripherals; use {defmt_rtt as _, panic_probe as _}; - macro_rules! forever { ($val:expr) => {{ type T = impl Sized; @@ -19,21 +20,29 @@ macro_rules! forever { }}; } +#[embassy::task] +async fn wifi_task(runner: cyw43::Runner<'static, PIN_23, PIN_25, PIN_29, PIN_24>) -> ! { + runner.run().await +} + #[embassy::main] -async fn main(_spawner: Spawner, p: Peripherals) { +async fn main(spawner: Spawner, p: Peripherals) { info!("Hello World!"); let (pwr, cs, clk, dio) = (p.PIN_23, p.PIN_25, p.PIN_29, p.PIN_24); //let (pwr, cs, clk, dio) = (p.PIN_23, p.PIN_0, p.PIN_1, p.PIN_2); - let mut driver = cyw43::Driver::new( + let state = forever!(cyw43::State::new()); + let (mut control, runner) = cyw43::new( + state, Output::new(pwr, Level::Low), Output::new(cs, Level::High), Output::new(clk, Level::Low), Flex::new(dio), - ); + ) + .await; - driver.init().await; + spawner.spawn(wifi_task(runner)).unwrap(); - loop {} + control.init().await; } diff --git a/rust-toolchain.toml b/rust-toolchain.toml new file mode 100644 index 00000000..03da4cf7 --- /dev/null +++ b/rust-toolchain.toml @@ -0,0 +1,8 @@ +# Before upgrading check that everything is available on all tier1 targets here: +# https://rust-lang.github.io/rustup-components-history +[toolchain] +channel = "nightly-2022-07-09" +components = [ "rust-src", "rustfmt" ] +targets = [ + "thumbv6m-none-eabi", +] diff --git a/src/lib.rs b/src/lib.rs index 5caf1926..3609ecae 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,16 +1,20 @@ #![no_std] #![no_main] -#![feature(type_alias_impl_trait, concat_bytes)] - +#![feature(type_alias_impl_trait, concat_bytes, const_slice_from_raw_parts)] // This mod MUST go first, so that the others see its macros. pub(crate) mod fmt; +mod structs; + +use core::cell::Cell; use core::slice; use embassy::time::{block_for, Duration, Timer}; use embassy::util::yield_now; use embassy_rp::gpio::{Flex, Output, Pin}; +use self::structs::*; + fn swap16(x: u32) -> u32 { (x & 0xFF00FF00) >> 8 | (x & 0x00FF00FF) << 8 } @@ -169,68 +173,73 @@ const CHIP: Chip = Chip { }; #[derive(Clone, Copy)] -#[repr(C)] -struct SdpcmHeader { - len: u16, - len_inv: u16, - /// Rx/Tx sequence number - sequence: u8, - /// 4 MSB Channel number, 4 LSB arbitrary flag - channel_and_flags: u8, - /// Length of next data frame, reserved for Tx - next_length: u8, - /// Data offset - header_length: u8, - /// Flow control bits, reserved for Tx - wireless_flow_control: u8, - /// Maximum Sequence number allowed by firmware for Tx - bus_data_credit: u8, - /// Reserved - reserved: [u8; 2], +enum IoctlState { + Idle, + + Pending { + kind: u32, + cmd: u32, + iface: u32, + buf: *const [u8], + }, + Sent, + Done, } -#[derive(Clone, Copy)] -#[repr(C)] -struct CdcHeader { - cmd: u32, - out_len: u16, - in_len: u16, - flags: u16, - id: u16, - status: u32, +pub struct State { + ioctl_id: Cell, + ioctl_state: Cell, } -#[derive(Clone, Copy)] -#[repr(C)] -struct BdcHeader { - flags: u8, - /// 802.1d Priority (low 3 bits) - priority: u8, - flags2: u8, - /// Offset from end of BDC header to packet data, in 4-uint8_t words. Leaves room for optional headers. - data_offset: u8, -} - -macro_rules! impl_bytes { - ($t:ident) => { - impl $t { - const SIZE: usize = core::mem::size_of::(); - - pub fn to_bytes(&self) -> [u8; Self::SIZE] { - unsafe { core::mem::transmute(*self) } - } - - pub fn from_bytes(bytes: &[u8; Self::SIZE]) -> Self { - unsafe { core::mem::transmute(*bytes) } - } +impl State { + pub fn new() -> Self { + Self { + ioctl_id: Cell::new(0), + ioctl_state: Cell::new(IoctlState::Idle), } - }; + } } -impl_bytes!(SdpcmHeader); -impl_bytes!(CdcHeader); -impl_bytes!(BdcHeader); -pub struct Driver<'a, PWR: Pin, CS: Pin, CLK: Pin, DIO: Pin> { +pub struct Control<'a> { + state: &'a State, +} + +impl<'a> Control<'a> { + pub async fn init(&mut self) { + let clm = unsafe { slice::from_raw_parts(0x10140000 as *const u8, 4752) }; + + let mut buf = [0; 8 + 12 + 1024]; + buf[0..8].copy_from_slice(b"clmload\x00"); + buf[8..20].copy_from_slice(b"\x02\x10\x02\x00\x00\x04\x00\x00\x00\x00\x00\x00"); + buf[20..].copy_from_slice(&clm[..1024]); + self.ioctl(2, 263, 0, &buf).await; + info!("IOCTL done"); + } + + async fn ioctl(&mut self, kind: u32, cmd: u32, iface: u32, buf: &[u8]) { + // TODO cancel ioctl on future drop. + + while !matches!(self.state.ioctl_state.get(), IoctlState::Idle) { + yield_now().await; + } + + self.state.ioctl_id.set(self.state.ioctl_id.get().wrapping_add(1)); + + self.state + .ioctl_state + .set(IoctlState::Pending { kind, cmd, iface, buf }); + + while !matches!(self.state.ioctl_state.get(), IoctlState::Done) { + yield_now().await; + } + + self.state.ioctl_state.set(IoctlState::Idle); + } +} + +pub struct Runner<'a, PWR: Pin, CS: Pin, CLK: Pin, DIO: Pin> { + state: &'a State, + pwr: Output<'a, PWR>, /// SPI chip-select. @@ -249,18 +258,29 @@ pub struct Driver<'a, PWR: Pin, CS: Pin, CLK: Pin, DIO: Pin> { backplane_window: u32, } -impl<'a, PWR: Pin, CS: Pin, CLK: Pin, DIO: Pin> Driver<'a, PWR, CS, CLK, DIO> { - pub fn new(pwr: Output<'a, PWR>, cs: Output<'a, CS>, clk: Output<'a, CLK>, dio: Flex<'a, DIO>) -> Self { - Self { - pwr, - cs, - clk, - dio, - backplane_window: 0xAAAA_AAAA, - } - } +pub async fn new<'a, PWR: Pin, CS: Pin, CLK: Pin, DIO: Pin>( + state: &'a State, + pwr: Output<'a, PWR>, + cs: Output<'a, CS>, + clk: Output<'a, CLK>, + dio: Flex<'a, DIO>, +) -> (Control<'a>, Runner<'a, PWR, CS, CLK, DIO>) { + let mut runner = Runner { + state, + pwr, + cs, + clk, + dio, + backplane_window: 0xAAAA_AAAA, + }; - pub async fn init(&mut self) { + runner.init().await; + + (Control { state }, runner) +} + +impl<'a, PWR: Pin, CS: Pin, CLK: Pin, DIO: Pin> Runner<'a, PWR, CS, CLK, DIO> { + async fn init(&mut self) { // Set strap to select gSPI mode. self.dio.set_as_output(); self.dio.set_low(); @@ -306,6 +326,8 @@ impl<'a, PWR: Pin, CS: Pin, CLK: Pin, DIO: Pin> Driver<'a, PWR, CS, CLK, DIO> { self.bp_write32(CHIP.socsram_base_address + 0x10, 3); self.bp_write32(CHIP.socsram_base_address + 0x44, 0); + let ram_addr = CHIP.atcm_ram_base_address; + // I'm flashing the firmwares independently at hardcoded addresses, instead of baking them // into the program with `include_bytes!` or similar, so that flashing the program stays fast. // @@ -314,9 +336,6 @@ impl<'a, PWR: Pin, CS: Pin, CLK: Pin, DIO: Pin> Driver<'a, PWR, CS, CLK, DIO> { // probe-rs-cli download 43439A0.bin --format bin --chip RP2040 --base-address 0x10100000 // probe-rs-cli download 43439A0.clm_blob --format bin --chip RP2040 --base-address 0x10140000 let fw = unsafe { slice::from_raw_parts(0x10100000 as *const u8, 224190) }; - let clm = unsafe { slice::from_raw_parts(0x10140000 as *const u8, 4752) }; - - let ram_addr = CHIP.atcm_ram_base_address; info!("loading fw"); self.bp_write(ram_addr, fw); @@ -374,17 +393,20 @@ impl<'a, PWR: Pin, CS: Pin, CLK: Pin, DIO: Pin> Driver<'a, PWR, CS, CLK, DIO> { let _ = self.read8(FUNC_BACKPLANE, REG_BACKPLANE_PULL_UP); */ - let mut buf = [0; 8 + 12 + 1024]; - buf[0..8].copy_from_slice(b"clmload\x00"); - buf[8..20].copy_from_slice(b"\x02\x10\x02\x00\x00\x04\x00\x00\x00\x00\x00\x00"); - buf[20..].copy_from_slice(&clm[..1024]); - self.send_ioctl(2, 263, 0, &buf); - info!("init done "); + } + pub async fn run(mut self) -> ! { let mut old_irq = 0; let mut buf = [0; 2048]; loop { + // Send stuff + if let IoctlState::Pending { kind, cmd, iface, buf } = self.state.ioctl_state.get() { + self.send_ioctl(kind, cmd, iface, unsafe { &*buf }, self.state.ioctl_id.get()); + self.state.ioctl_state.set(IoctlState::Sent); + } + + // Receive stuff let irq = self.read16(FUNC_BUS, REG_BUS_INTERRUPT); if irq != old_irq { info!("irq: {:04x}", irq); @@ -419,6 +441,7 @@ impl<'a, PWR: Pin, CS: Pin, CLK: Pin, DIO: Pin> Driver<'a, PWR, CS, CLK, DIO> { } } + // TODO use IRQs yield_now().await; } } @@ -453,14 +476,16 @@ impl<'a, PWR: Pin, CS: Pin, CLK: Pin, DIO: Pin> Driver<'a, PWR, CS, CLK, DIO> { let cdc_header = CdcHeader::from_bytes(packet[SdpcmHeader::SIZE..][..CdcHeader::SIZE].try_into().unwrap()); - // TODO check cdc_header.id matches - // TODO check status + if cdc_header.id == self.state.ioctl_id.get() { + assert_eq!(cdc_header.status, 0); // todo propagate error + self.state.ioctl_state.set(IoctlState::Done); + } } _ => {} } } - fn send_ioctl(&mut self, kind: u32, cmd: u32, iface: u32, data: &[u8]) { + fn send_ioctl(&mut self, kind: u32, cmd: u32, iface: u32, data: &[u8], id: u16) { let mut buf = [0; 2048]; let total_len = SdpcmHeader::SIZE + CdcHeader::SIZE + data.len(); @@ -469,7 +494,7 @@ impl<'a, PWR: Pin, CS: Pin, CLK: Pin, DIO: Pin> Driver<'a, PWR, CS, CLK, DIO> { len: total_len as u16, len_inv: !total_len as u16, sequence: 0x02, // todo - channel_and_flags: 0, // control channle + channel_and_flags: 0, // control channel next_length: 0, header_length: SdpcmHeader::SIZE as _, wireless_flow_control: 0, @@ -482,7 +507,7 @@ impl<'a, PWR: Pin, CS: Pin, CLK: Pin, DIO: Pin> Driver<'a, PWR, CS, CLK, DIO> { out_len: data.len() as _, in_len: 0, flags: kind as u16 | (iface as u16) << 12, - id: 1, // todo + id, status: 0, }; @@ -780,16 +805,13 @@ impl<'a, PWR: Pin, CS: Pin, CLK: Pin, DIO: Pin> Driver<'a, PWR, CS, CLK, DIO> { w |= 0x01; } self.clk.set_high(); - delay(); // falling edge self.clk.set_low(); - delay(); } *word = w } self.clk.set_low(); - delay(); } fn spi_write(&mut self, words: &[u8]) { @@ -804,25 +826,19 @@ impl<'a, PWR: Pin, CS: Pin, CLK: Pin, DIO: Pin> Driver<'a, PWR, CS, CLK, DIO> { } else { self.dio.set_high(); } - delay(); // rising edge self.clk.set_high(); - delay(); word = word << 1; } } self.clk.set_low(); - delay(); + self.dio.set_as_input(); } } -fn delay() { - //cortex_m::asm::delay(5); -} - macro_rules! nvram { ($($s:literal,)*) => { concat_bytes!($($s, b"\x00",)* b"\x00\x00") diff --git a/src/structs.rs b/src/structs.rs new file mode 100644 index 00000000..fe5e89a3 --- /dev/null +++ b/src/structs.rs @@ -0,0 +1,71 @@ +#[derive(Clone, Copy)] +#[repr(C)] +pub struct SdpcmHeader { + pub len: u16, + pub len_inv: u16, + /// Rx/Tx sequence number + pub sequence: u8, + /// 4 MSB Channel number, 4 LSB arbitrary flag + pub channel_and_flags: u8, + /// Length of next data frame, reserved for Tx + pub next_length: u8, + /// Data offset + pub header_length: u8, + /// Flow control bits, reserved for Tx + pub wireless_flow_control: u8, + /// Maximum Sequence number allowed by firmware for Tx + pub bus_data_credit: u8, + /// Reserved + pub reserved: [u8; 2], +} + +#[derive(Clone, Copy)] +#[repr(C)] +pub struct CdcHeader { + pub cmd: u32, + pub out_len: u16, + pub in_len: u16, + pub flags: u16, + pub id: u16, + pub status: u32, +} + +#[derive(Clone, Copy)] +#[repr(C)] +pub struct BdcHeader { + pub flags: u8, + /// 802.1d Priority (low 3 bits) + pub priority: u8, + pub flags2: u8, + /// Offset from end of BDC header to packet data, in 4-uint8_t words. Leaves room for optional headers. + pub data_offset: u8, +} + +#[derive(Clone, Copy)] +#[repr(C)] +pub struct DownloadHeader { + pub flag: u16, + pub dload_type: u16, + pub len: u32, + pub crc: u32, +} + +macro_rules! impl_bytes { + ($t:ident) => { + impl $t { + pub const SIZE: usize = core::mem::size_of::(); + + pub fn to_bytes(&self) -> [u8; Self::SIZE] { + unsafe { core::mem::transmute(*self) } + } + + pub fn from_bytes(bytes: &[u8; Self::SIZE]) -> Self { + unsafe { core::mem::transmute(*bytes) } + } + } + }; +} +impl_bytes!(SdpcmHeader); +impl_bytes!(CdcHeader); +impl_bytes!(BdcHeader); +impl_bytes!(DownloadHeader); From 7ddcacac7bbfaed303dcda7d14ab29cad94fd570 Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Mon, 11 Jul 2022 03:07:39 +0200 Subject: [PATCH 003/178] clm download, country config. --- src/countries.rs | 481 +++++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 98 ++++++++-- src/structs.rs | 56 ++++-- 3 files changed, 596 insertions(+), 39 deletions(-) create mode 100644 src/countries.rs diff --git a/src/countries.rs b/src/countries.rs new file mode 100644 index 00000000..fa1e8cac --- /dev/null +++ b/src/countries.rs @@ -0,0 +1,481 @@ +#![allow(unused)] + +pub struct Country { + pub code: [u8; 2], + pub rev: u16, +} + +/// AF Afghanistan +pub const AFGHANISTAN: Country = Country { code: *b"AF", rev: 0 }; +/// AL Albania +pub const ALBANIA: Country = Country { code: *b"AL", rev: 0 }; +/// DZ Algeria +pub const ALGERIA: Country = Country { code: *b"DZ", rev: 0 }; +/// AS American_Samoa +pub const AMERICAN_SAMOA: Country = Country { code: *b"AS", rev: 0 }; +/// AO Angola +pub const ANGOLA: Country = Country { code: *b"AO", rev: 0 }; +/// AI Anguilla +pub const ANGUILLA: Country = Country { code: *b"AI", rev: 0 }; +/// AG Antigua_and_Barbuda +pub const ANTIGUA_AND_BARBUDA: Country = Country { code: *b"AG", rev: 0 }; +/// AR Argentina +pub const ARGENTINA: Country = Country { code: *b"AR", rev: 0 }; +/// AM Armenia +pub const ARMENIA: Country = Country { code: *b"AM", rev: 0 }; +/// AW Aruba +pub const ARUBA: Country = Country { code: *b"AW", rev: 0 }; +/// AU Australia +pub const AUSTRALIA: Country = Country { code: *b"AU", rev: 0 }; +/// AT Austria +pub const AUSTRIA: Country = Country { code: *b"AT", rev: 0 }; +/// AZ Azerbaijan +pub const AZERBAIJAN: Country = Country { code: *b"AZ", rev: 0 }; +/// BS Bahamas +pub const BAHAMAS: Country = Country { code: *b"BS", rev: 0 }; +/// BH Bahrain +pub const BAHRAIN: Country = Country { code: *b"BH", rev: 0 }; +/// 0B Baker_Island +pub const BAKER_ISLAND: Country = Country { code: *b"0B", rev: 0 }; +/// BD Bangladesh +pub const BANGLADESH: Country = Country { code: *b"BD", rev: 0 }; +/// BB Barbados +pub const BARBADOS: Country = Country { code: *b"BB", rev: 0 }; +/// BY Belarus +pub const BELARUS: Country = Country { code: *b"BY", rev: 0 }; +/// BE Belgium +pub const BELGIUM: Country = Country { code: *b"BE", rev: 0 }; +/// BZ Belize +pub const BELIZE: Country = Country { code: *b"BZ", rev: 0 }; +/// BJ Benin +pub const BENIN: Country = Country { code: *b"BJ", rev: 0 }; +/// BM Bermuda +pub const BERMUDA: Country = Country { code: *b"BM", rev: 0 }; +/// BT Bhutan +pub const BHUTAN: Country = Country { code: *b"BT", rev: 0 }; +/// BO Bolivia +pub const BOLIVIA: Country = Country { code: *b"BO", rev: 0 }; +/// BA Bosnia_and_Herzegovina +pub const BOSNIA_AND_HERZEGOVINA: Country = Country { code: *b"BA", rev: 0 }; +/// BW Botswana +pub const BOTSWANA: Country = Country { code: *b"BW", rev: 0 }; +/// BR Brazil +pub const BRAZIL: Country = Country { code: *b"BR", rev: 0 }; +/// IO British_Indian_Ocean_Territory +pub const BRITISH_INDIAN_OCEAN_TERRITORY: Country = Country { code: *b"IO", rev: 0 }; +/// BN Brunei_Darussalam +pub const BRUNEI_DARUSSALAM: Country = Country { code: *b"BN", rev: 0 }; +/// BG Bulgaria +pub const BULGARIA: Country = Country { code: *b"BG", rev: 0 }; +/// BF Burkina_Faso +pub const BURKINA_FASO: Country = Country { code: *b"BF", rev: 0 }; +/// BI Burundi +pub const BURUNDI: Country = Country { code: *b"BI", rev: 0 }; +/// KH Cambodia +pub const CAMBODIA: Country = Country { code: *b"KH", rev: 0 }; +/// CM Cameroon +pub const CAMEROON: Country = Country { code: *b"CM", rev: 0 }; +/// CA Canada +pub const CANADA: Country = Country { code: *b"CA", rev: 0 }; +/// CA Canada Revision 950 +pub const CANADA_REV950: Country = Country { code: *b"CA", rev: 950 }; +/// CV Cape_Verde +pub const CAPE_VERDE: Country = Country { code: *b"CV", rev: 0 }; +/// KY Cayman_Islands +pub const CAYMAN_ISLANDS: Country = Country { code: *b"KY", rev: 0 }; +/// CF Central_African_Republic +pub const CENTRAL_AFRICAN_REPUBLIC: Country = Country { code: *b"CF", rev: 0 }; +/// TD Chad +pub const CHAD: Country = Country { code: *b"TD", rev: 0 }; +/// CL Chile +pub const CHILE: Country = Country { code: *b"CL", rev: 0 }; +/// CN China +pub const CHINA: Country = Country { code: *b"CN", rev: 0 }; +/// CX Christmas_Island +pub const CHRISTMAS_ISLAND: Country = Country { code: *b"CX", rev: 0 }; +/// CO Colombia +pub const COLOMBIA: Country = Country { code: *b"CO", rev: 0 }; +/// KM Comoros +pub const COMOROS: Country = Country { code: *b"KM", rev: 0 }; +/// CG Congo +pub const CONGO: Country = Country { code: *b"CG", rev: 0 }; +/// CD Congo,_The_Democratic_Republic_Of_The +pub const CONGO_THE_DEMOCRATIC_REPUBLIC_OF_THE: Country = Country { code: *b"CD", rev: 0 }; +/// CR Costa_Rica +pub const COSTA_RICA: Country = Country { code: *b"CR", rev: 0 }; +/// CI Cote_D'ivoire +pub const COTE_DIVOIRE: Country = Country { code: *b"CI", rev: 0 }; +/// HR Croatia +pub const CROATIA: Country = Country { code: *b"HR", rev: 0 }; +/// CU Cuba +pub const CUBA: Country = Country { code: *b"CU", rev: 0 }; +/// CY Cyprus +pub const CYPRUS: Country = Country { code: *b"CY", rev: 0 }; +/// CZ Czech_Republic +pub const CZECH_REPUBLIC: Country = Country { code: *b"CZ", rev: 0 }; +/// DK Denmark +pub const DENMARK: Country = Country { code: *b"DK", rev: 0 }; +/// DJ Djibouti +pub const DJIBOUTI: Country = Country { code: *b"DJ", rev: 0 }; +/// DM Dominica +pub const DOMINICA: Country = Country { code: *b"DM", rev: 0 }; +/// DO Dominican_Republic +pub const DOMINICAN_REPUBLIC: Country = Country { code: *b"DO", rev: 0 }; +/// AU G'Day mate! +pub const DOWN_UNDER: Country = Country { code: *b"AU", rev: 0 }; +/// EC Ecuador +pub const ECUADOR: Country = Country { code: *b"EC", rev: 0 }; +/// EG Egypt +pub const EGYPT: Country = Country { code: *b"EG", rev: 0 }; +/// SV El_Salvador +pub const EL_SALVADOR: Country = Country { code: *b"SV", rev: 0 }; +/// GQ Equatorial_Guinea +pub const EQUATORIAL_GUINEA: Country = Country { code: *b"GQ", rev: 0 }; +/// ER Eritrea +pub const ERITREA: Country = Country { code: *b"ER", rev: 0 }; +/// EE Estonia +pub const ESTONIA: Country = Country { code: *b"EE", rev: 0 }; +/// ET Ethiopia +pub const ETHIOPIA: Country = Country { code: *b"ET", rev: 0 }; +/// FK Falkland_Islands_(Malvinas) +pub const FALKLAND_ISLANDS_MALVINAS: Country = Country { code: *b"FK", rev: 0 }; +/// FO Faroe_Islands +pub const FAROE_ISLANDS: Country = Country { code: *b"FO", rev: 0 }; +/// FJ Fiji +pub const FIJI: Country = Country { code: *b"FJ", rev: 0 }; +/// FI Finland +pub const FINLAND: Country = Country { code: *b"FI", rev: 0 }; +/// FR France +pub const FRANCE: Country = Country { code: *b"FR", rev: 0 }; +/// GF French_Guina +pub const FRENCH_GUINA: Country = Country { code: *b"GF", rev: 0 }; +/// PF French_Polynesia +pub const FRENCH_POLYNESIA: Country = Country { code: *b"PF", rev: 0 }; +/// TF French_Southern_Territories +pub const FRENCH_SOUTHERN_TERRITORIES: Country = Country { code: *b"TF", rev: 0 }; +/// GA Gabon +pub const GABON: Country = Country { code: *b"GA", rev: 0 }; +/// GM Gambia +pub const GAMBIA: Country = Country { code: *b"GM", rev: 0 }; +/// GE Georgia +pub const GEORGIA: Country = Country { code: *b"GE", rev: 0 }; +/// DE Germany +pub const GERMANY: Country = Country { code: *b"DE", rev: 0 }; +/// E0 European_Wide Revision 895 +pub const EUROPEAN_WIDE_REV895: Country = Country { code: *b"E0", rev: 895 }; +/// GH Ghana +pub const GHANA: Country = Country { code: *b"GH", rev: 0 }; +/// GI Gibraltar +pub const GIBRALTAR: Country = Country { code: *b"GI", rev: 0 }; +/// GR Greece +pub const GREECE: Country = Country { code: *b"GR", rev: 0 }; +/// GD Grenada +pub const GRENADA: Country = Country { code: *b"GD", rev: 0 }; +/// GP Guadeloupe +pub const GUADELOUPE: Country = Country { code: *b"GP", rev: 0 }; +/// GU Guam +pub const GUAM: Country = Country { code: *b"GU", rev: 0 }; +/// GT Guatemala +pub const GUATEMALA: Country = Country { code: *b"GT", rev: 0 }; +/// GG Guernsey +pub const GUERNSEY: Country = Country { code: *b"GG", rev: 0 }; +/// GN Guinea +pub const GUINEA: Country = Country { code: *b"GN", rev: 0 }; +/// GW Guinea-bissau +pub const GUINEA_BISSAU: Country = Country { code: *b"GW", rev: 0 }; +/// GY Guyana +pub const GUYANA: Country = Country { code: *b"GY", rev: 0 }; +/// HT Haiti +pub const HAITI: Country = Country { code: *b"HT", rev: 0 }; +/// VA Holy_See_(Vatican_City_State) +pub const HOLY_SEE_VATICAN_CITY_STATE: Country = Country { code: *b"VA", rev: 0 }; +/// HN Honduras +pub const HONDURAS: Country = Country { code: *b"HN", rev: 0 }; +/// HK Hong_Kong +pub const HONG_KONG: Country = Country { code: *b"HK", rev: 0 }; +/// HU Hungary +pub const HUNGARY: Country = Country { code: *b"HU", rev: 0 }; +/// IS Iceland +pub const ICELAND: Country = Country { code: *b"IS", rev: 0 }; +/// IN India +pub const INDIA: Country = Country { code: *b"IN", rev: 0 }; +/// ID Indonesia +pub const INDONESIA: Country = Country { code: *b"ID", rev: 0 }; +/// IR Iran,_Islamic_Republic_Of +pub const IRAN_ISLAMIC_REPUBLIC_OF: Country = Country { code: *b"IR", rev: 0 }; +/// IQ Iraq +pub const IRAQ: Country = Country { code: *b"IQ", rev: 0 }; +/// IE Ireland +pub const IRELAND: Country = Country { code: *b"IE", rev: 0 }; +/// IL Israel +pub const ISRAEL: Country = Country { code: *b"IL", rev: 0 }; +/// IT Italy +pub const ITALY: Country = Country { code: *b"IT", rev: 0 }; +/// JM Jamaica +pub const JAMAICA: Country = Country { code: *b"JM", rev: 0 }; +/// JP Japan +pub const JAPAN: Country = Country { code: *b"JP", rev: 0 }; +/// JE Jersey +pub const JERSEY: Country = Country { code: *b"JE", rev: 0 }; +/// JO Jordan +pub const JORDAN: Country = Country { code: *b"JO", rev: 0 }; +/// KZ Kazakhstan +pub const KAZAKHSTAN: Country = Country { code: *b"KZ", rev: 0 }; +/// KE Kenya +pub const KENYA: Country = Country { code: *b"KE", rev: 0 }; +/// KI Kiribati +pub const KIRIBATI: Country = Country { code: *b"KI", rev: 0 }; +/// KR Korea,_Republic_Of +pub const KOREA_REPUBLIC_OF: Country = Country { code: *b"KR", rev: 1 }; +/// 0A Kosovo +pub const KOSOVO: Country = Country { code: *b"0A", rev: 0 }; +/// KW Kuwait +pub const KUWAIT: Country = Country { code: *b"KW", rev: 0 }; +/// KG Kyrgyzstan +pub const KYRGYZSTAN: Country = Country { code: *b"KG", rev: 0 }; +/// LA Lao_People's_Democratic_Repubic +pub const LAO_PEOPLES_DEMOCRATIC_REPUBIC: Country = Country { code: *b"LA", rev: 0 }; +/// LV Latvia +pub const LATVIA: Country = Country { code: *b"LV", rev: 0 }; +/// LB Lebanon +pub const LEBANON: Country = Country { code: *b"LB", rev: 0 }; +/// LS Lesotho +pub const LESOTHO: Country = Country { code: *b"LS", rev: 0 }; +/// LR Liberia +pub const LIBERIA: Country = Country { code: *b"LR", rev: 0 }; +/// LY Libyan_Arab_Jamahiriya +pub const LIBYAN_ARAB_JAMAHIRIYA: Country = Country { code: *b"LY", rev: 0 }; +/// LI Liechtenstein +pub const LIECHTENSTEIN: Country = Country { code: *b"LI", rev: 0 }; +/// LT Lithuania +pub const LITHUANIA: Country = Country { code: *b"LT", rev: 0 }; +/// LU Luxembourg +pub const LUXEMBOURG: Country = Country { code: *b"LU", rev: 0 }; +/// MO Macao +pub const MACAO: Country = Country { code: *b"MO", rev: 0 }; +/// MK Macedonia,_Former_Yugoslav_Republic_Of +pub const MACEDONIA_FORMER_YUGOSLAV_REPUBLIC_OF: Country = Country { code: *b"MK", rev: 0 }; +/// MG Madagascar +pub const MADAGASCAR: Country = Country { code: *b"MG", rev: 0 }; +/// MW Malawi +pub const MALAWI: Country = Country { code: *b"MW", rev: 0 }; +/// MY Malaysia +pub const MALAYSIA: Country = Country { code: *b"MY", rev: 0 }; +/// MV Maldives +pub const MALDIVES: Country = Country { code: *b"MV", rev: 0 }; +/// ML Mali +pub const MALI: Country = Country { code: *b"ML", rev: 0 }; +/// MT Malta +pub const MALTA: Country = Country { code: *b"MT", rev: 0 }; +/// IM Man,_Isle_Of +pub const MAN_ISLE_OF: Country = Country { code: *b"IM", rev: 0 }; +/// MQ Martinique +pub const MARTINIQUE: Country = Country { code: *b"MQ", rev: 0 }; +/// MR Mauritania +pub const MAURITANIA: Country = Country { code: *b"MR", rev: 0 }; +/// MU Mauritius +pub const MAURITIUS: Country = Country { code: *b"MU", rev: 0 }; +/// YT Mayotte +pub const MAYOTTE: Country = Country { code: *b"YT", rev: 0 }; +/// MX Mexico +pub const MEXICO: Country = Country { code: *b"MX", rev: 0 }; +/// FM Micronesia,_Federated_States_Of +pub const MICRONESIA_FEDERATED_STATES_OF: Country = Country { code: *b"FM", rev: 0 }; +/// MD Moldova,_Republic_Of +pub const MOLDOVA_REPUBLIC_OF: Country = Country { code: *b"MD", rev: 0 }; +/// MC Monaco +pub const MONACO: Country = Country { code: *b"MC", rev: 0 }; +/// MN Mongolia +pub const MONGOLIA: Country = Country { code: *b"MN", rev: 0 }; +/// ME Montenegro +pub const MONTENEGRO: Country = Country { code: *b"ME", rev: 0 }; +/// MS Montserrat +pub const MONTSERRAT: Country = Country { code: *b"MS", rev: 0 }; +/// MA Morocco +pub const MOROCCO: Country = Country { code: *b"MA", rev: 0 }; +/// MZ Mozambique +pub const MOZAMBIQUE: Country = Country { code: *b"MZ", rev: 0 }; +/// MM Myanmar +pub const MYANMAR: Country = Country { code: *b"MM", rev: 0 }; +/// NA Namibia +pub const NAMIBIA: Country = Country { code: *b"NA", rev: 0 }; +/// NR Nauru +pub const NAURU: Country = Country { code: *b"NR", rev: 0 }; +/// NP Nepal +pub const NEPAL: Country = Country { code: *b"NP", rev: 0 }; +/// NL Netherlands +pub const NETHERLANDS: Country = Country { code: *b"NL", rev: 0 }; +/// AN Netherlands_Antilles +pub const NETHERLANDS_ANTILLES: Country = Country { code: *b"AN", rev: 0 }; +/// NC New_Caledonia +pub const NEW_CALEDONIA: Country = Country { code: *b"NC", rev: 0 }; +/// NZ New_Zealand +pub const NEW_ZEALAND: Country = Country { code: *b"NZ", rev: 0 }; +/// NI Nicaragua +pub const NICARAGUA: Country = Country { code: *b"NI", rev: 0 }; +/// NE Niger +pub const NIGER: Country = Country { code: *b"NE", rev: 0 }; +/// NG Nigeria +pub const NIGERIA: Country = Country { code: *b"NG", rev: 0 }; +/// NF Norfolk_Island +pub const NORFOLK_ISLAND: Country = Country { code: *b"NF", rev: 0 }; +/// MP Northern_Mariana_Islands +pub const NORTHERN_MARIANA_ISLANDS: Country = Country { code: *b"MP", rev: 0 }; +/// NO Norway +pub const NORWAY: Country = Country { code: *b"NO", rev: 0 }; +/// OM Oman +pub const OMAN: Country = Country { code: *b"OM", rev: 0 }; +/// PK Pakistan +pub const PAKISTAN: Country = Country { code: *b"PK", rev: 0 }; +/// PW Palau +pub const PALAU: Country = Country { code: *b"PW", rev: 0 }; +/// PA Panama +pub const PANAMA: Country = Country { code: *b"PA", rev: 0 }; +/// PG Papua_New_Guinea +pub const PAPUA_NEW_GUINEA: Country = Country { code: *b"PG", rev: 0 }; +/// PY Paraguay +pub const PARAGUAY: Country = Country { code: *b"PY", rev: 0 }; +/// PE Peru +pub const PERU: Country = Country { code: *b"PE", rev: 0 }; +/// PH Philippines +pub const PHILIPPINES: Country = Country { code: *b"PH", rev: 0 }; +/// PL Poland +pub const POLAND: Country = Country { code: *b"PL", rev: 0 }; +/// PT Portugal +pub const PORTUGAL: Country = Country { code: *b"PT", rev: 0 }; +/// PR Pueto_Rico +pub const PUETO_RICO: Country = Country { code: *b"PR", rev: 0 }; +/// QA Qatar +pub const QATAR: Country = Country { code: *b"QA", rev: 0 }; +/// RE Reunion +pub const REUNION: Country = Country { code: *b"RE", rev: 0 }; +/// RO Romania +pub const ROMANIA: Country = Country { code: *b"RO", rev: 0 }; +/// RU Russian_Federation +pub const RUSSIAN_FEDERATION: Country = Country { code: *b"RU", rev: 0 }; +/// RW Rwanda +pub const RWANDA: Country = Country { code: *b"RW", rev: 0 }; +/// KN Saint_Kitts_and_Nevis +pub const SAINT_KITTS_AND_NEVIS: Country = Country { code: *b"KN", rev: 0 }; +/// LC Saint_Lucia +pub const SAINT_LUCIA: Country = Country { code: *b"LC", rev: 0 }; +/// PM Saint_Pierre_and_Miquelon +pub const SAINT_PIERRE_AND_MIQUELON: Country = Country { code: *b"PM", rev: 0 }; +/// VC Saint_Vincent_and_The_Grenadines +pub const SAINT_VINCENT_AND_THE_GRENADINES: Country = Country { code: *b"VC", rev: 0 }; +/// WS Samoa +pub const SAMOA: Country = Country { code: *b"WS", rev: 0 }; +/// MF Sanit_Martin_/_Sint_Marteen +pub const SANIT_MARTIN_SINT_MARTEEN: Country = Country { code: *b"MF", rev: 0 }; +/// ST Sao_Tome_and_Principe +pub const SAO_TOME_AND_PRINCIPE: Country = Country { code: *b"ST", rev: 0 }; +/// SA Saudi_Arabia +pub const SAUDI_ARABIA: Country = Country { code: *b"SA", rev: 0 }; +/// SN Senegal +pub const SENEGAL: Country = Country { code: *b"SN", rev: 0 }; +/// RS Serbia +pub const SERBIA: Country = Country { code: *b"RS", rev: 0 }; +/// SC Seychelles +pub const SEYCHELLES: Country = Country { code: *b"SC", rev: 0 }; +/// SL Sierra_Leone +pub const SIERRA_LEONE: Country = Country { code: *b"SL", rev: 0 }; +/// SG Singapore +pub const SINGAPORE: Country = Country { code: *b"SG", rev: 0 }; +/// SK Slovakia +pub const SLOVAKIA: Country = Country { code: *b"SK", rev: 0 }; +/// SI Slovenia +pub const SLOVENIA: Country = Country { code: *b"SI", rev: 0 }; +/// SB Solomon_Islands +pub const SOLOMON_ISLANDS: Country = Country { code: *b"SB", rev: 0 }; +/// SO Somalia +pub const SOMALIA: Country = Country { code: *b"SO", rev: 0 }; +/// ZA South_Africa +pub const SOUTH_AFRICA: Country = Country { code: *b"ZA", rev: 0 }; +/// ES Spain +pub const SPAIN: Country = Country { code: *b"ES", rev: 0 }; +/// LK Sri_Lanka +pub const SRI_LANKA: Country = Country { code: *b"LK", rev: 0 }; +/// SR Suriname +pub const SURINAME: Country = Country { code: *b"SR", rev: 0 }; +/// SZ Swaziland +pub const SWAZILAND: Country = Country { code: *b"SZ", rev: 0 }; +/// SE Sweden +pub const SWEDEN: Country = Country { code: *b"SE", rev: 0 }; +/// CH Switzerland +pub const SWITZERLAND: Country = Country { code: *b"CH", rev: 0 }; +/// SY Syrian_Arab_Republic +pub const SYRIAN_ARAB_REPUBLIC: Country = Country { code: *b"SY", rev: 0 }; +/// TW Taiwan,_Province_Of_China +pub const TAIWAN_PROVINCE_OF_CHINA: Country = Country { code: *b"TW", rev: 0 }; +/// TJ Tajikistan +pub const TAJIKISTAN: Country = Country { code: *b"TJ", rev: 0 }; +/// TZ Tanzania,_United_Republic_Of +pub const TANZANIA_UNITED_REPUBLIC_OF: Country = Country { code: *b"TZ", rev: 0 }; +/// TH Thailand +pub const THAILAND: Country = Country { code: *b"TH", rev: 0 }; +/// TG Togo +pub const TOGO: Country = Country { code: *b"TG", rev: 0 }; +/// TO Tonga +pub const TONGA: Country = Country { code: *b"TO", rev: 0 }; +/// TT Trinidad_and_Tobago +pub const TRINIDAD_AND_TOBAGO: Country = Country { code: *b"TT", rev: 0 }; +/// TN Tunisia +pub const TUNISIA: Country = Country { code: *b"TN", rev: 0 }; +/// TR Turkey +pub const TURKEY: Country = Country { code: *b"TR", rev: 0 }; +/// TM Turkmenistan +pub const TURKMENISTAN: Country = Country { code: *b"TM", rev: 0 }; +/// TC Turks_and_Caicos_Islands +pub const TURKS_AND_CAICOS_ISLANDS: Country = Country { code: *b"TC", rev: 0 }; +/// TV Tuvalu +pub const TUVALU: Country = Country { code: *b"TV", rev: 0 }; +/// UG Uganda +pub const UGANDA: Country = Country { code: *b"UG", rev: 0 }; +/// UA Ukraine +pub const UKRAINE: Country = Country { code: *b"UA", rev: 0 }; +/// AE United_Arab_Emirates +pub const UNITED_ARAB_EMIRATES: Country = Country { code: *b"AE", rev: 0 }; +/// GB United_Kingdom +pub const UNITED_KINGDOM: Country = Country { code: *b"GB", rev: 0 }; +/// US United_States +pub const UNITED_STATES: Country = Country { code: *b"US", rev: 0 }; +/// US United_States Revision 4 +pub const UNITED_STATES_REV4: Country = Country { code: *b"US", rev: 4 }; +/// Q1 United_States Revision 931 +pub const UNITED_STATES_REV931: Country = Country { code: *b"Q1", rev: 931 }; +/// Q2 United_States_(No_DFS) +pub const UNITED_STATES_NO_DFS: Country = Country { code: *b"Q2", rev: 0 }; +/// UM United_States_Minor_Outlying_Islands +pub const UNITED_STATES_MINOR_OUTLYING_ISLANDS: Country = Country { code: *b"UM", rev: 0 }; +/// UY Uruguay +pub const URUGUAY: Country = Country { code: *b"UY", rev: 0 }; +/// UZ Uzbekistan +pub const UZBEKISTAN: Country = Country { code: *b"UZ", rev: 0 }; +/// VU Vanuatu +pub const VANUATU: Country = Country { code: *b"VU", rev: 0 }; +/// VE Venezuela +pub const VENEZUELA: Country = Country { code: *b"VE", rev: 0 }; +/// VN Viet_Nam +pub const VIET_NAM: Country = Country { code: *b"VN", rev: 0 }; +/// VG Virgin_Islands,_British +pub const VIRGIN_ISLANDS_BRITISH: Country = Country { code: *b"VG", rev: 0 }; +/// VI Virgin_Islands,_U.S. +pub const VIRGIN_ISLANDS_US: Country = Country { code: *b"VI", rev: 0 }; +/// WF Wallis_and_Futuna +pub const WALLIS_AND_FUTUNA: Country = Country { code: *b"WF", rev: 0 }; +/// 0C West_Bank +pub const WEST_BANK: Country = Country { code: *b"0C", rev: 0 }; +/// EH Western_Sahara +pub const WESTERN_SAHARA: Country = Country { code: *b"EH", rev: 0 }; +/// Worldwide Locale Revision 983 +pub const WORLD_WIDE_XV_REV983: Country = Country { code: *b"XV", rev: 983 }; +/// Worldwide Locale (passive Ch12-14) +pub const WORLD_WIDE_XX: Country = Country { code: *b"XX", rev: 0 }; +/// Worldwide Locale (passive Ch12-14) Revision 17 +pub const WORLD_WIDE_XX_REV17: Country = Country { code: *b"XX", rev: 17 }; +/// YE Yemen +pub const YEMEN: Country = Country { code: *b"YE", rev: 0 }; +/// ZM Zambia +pub const ZAMBIA: Country = Country { code: *b"ZM", rev: 0 }; +/// ZW Zimbabwe +pub const ZIMBABWE: Country = Country { code: *b"ZW", rev: 0 }; diff --git a/src/lib.rs b/src/lib.rs index 3609ecae..698c52f4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,9 +1,12 @@ #![no_std] #![no_main] #![feature(type_alias_impl_trait, concat_bytes, const_slice_from_raw_parts)] +#![deny(unused_must_use)] + // This mod MUST go first, so that the others see its macros. pub(crate) mod fmt; +mod countries; mod structs; use core::cell::Cell; @@ -206,14 +209,67 @@ pub struct Control<'a> { impl<'a> Control<'a> { pub async fn init(&mut self) { + const CHUNK_SIZE: usize = 1024; + let clm = unsafe { slice::from_raw_parts(0x10140000 as *const u8, 4752) }; - let mut buf = [0; 8 + 12 + 1024]; - buf[0..8].copy_from_slice(b"clmload\x00"); - buf[8..20].copy_from_slice(b"\x02\x10\x02\x00\x00\x04\x00\x00\x00\x00\x00\x00"); - buf[20..].copy_from_slice(&clm[..1024]); - self.ioctl(2, 263, 0, &buf).await; - info!("IOCTL done"); + info!("Downloading CLM..."); + + let mut offs = 0; + for chunk in clm.chunks(CHUNK_SIZE) { + let mut flag = DOWNLOAD_FLAG_HANDLER_VER; + if offs == 0 { + flag |= DOWNLOAD_FLAG_BEGIN; + } + offs += chunk.len(); + if offs == clm.len() { + flag |= DOWNLOAD_FLAG_END; + } + + let header = DownloadHeader { + flag, + dload_type: DOWNLOAD_TYPE_CLM, + len: chunk.len() as _, + crc: 0, + }; + let mut buf = [0; 8 + 12 + CHUNK_SIZE]; + buf[0..8].copy_from_slice(b"clmload\x00"); + buf[8..20].copy_from_slice(&header.to_bytes()); + buf[20..][..chunk.len()].copy_from_slice(&chunk); + self.ioctl(2, 263, 0, &buf[..8 + 12 + chunk.len()]).await; + } + + info!("Configuring misc stuff..."); + + self.set_iovar_u32("bus:txglom", 0).await; + self.set_iovar_u32("apsta", 1).await; + self.set_iovar("cur_etheraddr", &[02, 03, 04, 05, 06, 07]).await; + + let country = countries::WORLD_WIDE_XX; + let country_info = CountryInfo { + country_abbrev: [country.code[0], country.code[1], 0, 0], + country_code: [country.code[0], country.code[1], 0, 0], + rev: if country.rev == 0 { -1 } else { country.rev as _ }, + }; + self.set_iovar("country", &country_info.to_bytes()).await; + + info!("INIT DONE"); + } + + async fn set_iovar_u32(&mut self, name: &str, val: u32) { + self.set_iovar(name, &val.to_le_bytes()).await + } + + async fn set_iovar(&mut self, name: &str, val: &[u8]) { + info!("set {} = {:02x}", name, val); + + let mut buf = [0; 64]; + buf[..name.len()].copy_from_slice(name.as_bytes()); + buf[name.len()] = 0; + buf[name.len() + 1..][..val.len()].copy_from_slice(val); + + let total_len = name.len() + 1 + val.len(); + self.ioctl(2, 263, 0, &buf[..total_len]).await; } async fn ioctl(&mut self, kind: u32, cmd: u32, iface: u32, buf: &[u8]) { @@ -255,6 +311,7 @@ pub struct Runner<'a, PWR: Pin, CS: Pin, CLK: Pin, DIO: Pin> { /// - strap to set to gSPI mode on boot. dio: Flex<'a, DIO>, + ioctl_seq: u8, backplane_window: u32, } @@ -271,6 +328,8 @@ pub async fn new<'a, PWR: Pin, CS: Pin, CLK: Pin, DIO: Pin>( cs, clk, dio, + + ioctl_seq: 0, backplane_window: 0xAAAA_AAAA, }; @@ -397,7 +456,6 @@ impl<'a, PWR: Pin, CS: Pin, CLK: Pin, DIO: Pin> Runner<'a, PWR, CS, CLK, DIO> { } pub async fn run(mut self) -> ! { - let mut old_irq = 0; let mut buf = [0; 2048]; loop { // Send stuff @@ -408,10 +466,6 @@ impl<'a, PWR: Pin, CS: Pin, CLK: Pin, DIO: Pin> Runner<'a, PWR, CS, CLK, DIO> { // Receive stuff let irq = self.read16(FUNC_BUS, REG_BUS_INTERRUPT); - if irq != old_irq { - info!("irq: {:04x}", irq); - } - old_irq = irq; if irq & IRQ_F2_PACKET_AVAILABLE != 0 { let mut status = 0xFFFF_FFFF; @@ -421,7 +475,6 @@ impl<'a, PWR: Pin, CS: Pin, CLK: Pin, DIO: Pin> Runner<'a, PWR, CS, CLK, DIO> { if status & STATUS_F2_PKT_AVAILABLE != 0 { let len = (status & STATUS_F2_PKT_LEN_MASK) >> STATUS_F2_PKT_LEN_SHIFT; - info!("got len {}", len); let cmd = cmd_word(false, true, FUNC_WLAN, 0, len); @@ -435,7 +488,7 @@ impl<'a, PWR: Pin, CS: Pin, CLK: Pin, DIO: Pin> Runner<'a, PWR, CS, CLK, DIO> { } self.cs.set_high(); - info!("rxd packet {:02x}", &buf[..len as usize]); + info!("rx {:02x}", &buf[..(len as usize).min(48)]); self.rx(&buf[..len as usize]); } @@ -466,15 +519,17 @@ impl<'a, PWR: Pin, CS: Pin, CLK: Pin, DIO: Pin> Runner<'a, PWR, CS, CLK, DIO> { let channel = sdpcm_header.channel_and_flags & 0x0f; + let payload = &packet[sdpcm_header.header_length as _..]; + match channel { 0 => { - if packet.len() < SdpcmHeader::SIZE + CdcHeader::SIZE { - warn!("control packet too short, len={}", packet.len()); + if payload.len() < CdcHeader::SIZE { + warn!("payload too short, len={}", payload.len()); return; } let cdc_header = - CdcHeader::from_bytes(packet[SdpcmHeader::SIZE..][..CdcHeader::SIZE].try_into().unwrap()); + CdcHeader::from_bytes(payload[..CdcHeader::SIZE].try_into().unwrap()); if cdc_header.id == self.state.ioctl_id.get() { assert_eq!(cdc_header.status, 0); // todo propagate error @@ -490,10 +545,13 @@ impl<'a, PWR: Pin, CS: Pin, CLK: Pin, DIO: Pin> Runner<'a, PWR, CS, CLK, DIO> { let total_len = SdpcmHeader::SIZE + CdcHeader::SIZE + data.len(); + let seq = self.ioctl_seq; + self.ioctl_seq = self.ioctl_seq.wrapping_add(1); + let sdpcm_header = SdpcmHeader { - len: total_len as u16, + len: total_len as u16, // TODO does this len need to be rounded up to u32? len_inv: !total_len as u16, - sequence: 0x02, // todo + sequence: seq, channel_and_flags: 0, // control channel next_length: 0, header_length: SdpcmHeader::SIZE as _, @@ -515,7 +573,9 @@ impl<'a, PWR: Pin, CS: Pin, CLK: Pin, DIO: Pin> Runner<'a, PWR, CS, CLK, DIO> { buf[SdpcmHeader::SIZE..][..CdcHeader::SIZE].copy_from_slice(&cdc_header.to_bytes()); buf[SdpcmHeader::SIZE + CdcHeader::SIZE..][..data.len()].copy_from_slice(data); - info!("txing {:02x}", &buf[..total_len]); + let total_len = (total_len + 3) & !3; // round up to 4byte + + info!("tx {:02x}", &buf[..total_len.min(48)]); let cmd = cmd_word(true, true, FUNC_WLAN, 0, total_len as _); self.cs.set_low(); diff --git a/src/structs.rs b/src/structs.rs index fe5e89a3..bce9ab9f 100644 --- a/src/structs.rs +++ b/src/structs.rs @@ -1,3 +1,19 @@ +macro_rules! impl_bytes { + ($t:ident) => { + impl $t { + pub const SIZE: usize = core::mem::size_of::(); + + pub fn to_bytes(&self) -> [u8; Self::SIZE] { + unsafe { core::mem::transmute(*self) } + } + + pub fn from_bytes(bytes: &[u8; Self::SIZE]) -> Self { + unsafe { core::mem::transmute(*bytes) } + } + } + }; +} + #[derive(Clone, Copy)] #[repr(C)] pub struct SdpcmHeader { @@ -18,6 +34,7 @@ pub struct SdpcmHeader { /// Reserved pub reserved: [u8; 2], } +impl_bytes!(SdpcmHeader); #[derive(Clone, Copy)] #[repr(C)] @@ -29,6 +46,7 @@ pub struct CdcHeader { pub id: u16, pub status: u32, } +impl_bytes!(CdcHeader); #[derive(Clone, Copy)] #[repr(C)] @@ -40,32 +58,30 @@ pub struct BdcHeader { /// Offset from end of BDC header to packet data, in 4-uint8_t words. Leaves room for optional headers. pub data_offset: u8, } +impl_bytes!(BdcHeader); #[derive(Clone, Copy)] #[repr(C)] pub struct DownloadHeader { - pub flag: u16, + pub flag: u16, // pub dload_type: u16, pub len: u32, pub crc: u32, } - -macro_rules! impl_bytes { - ($t:ident) => { - impl $t { - pub const SIZE: usize = core::mem::size_of::(); - - pub fn to_bytes(&self) -> [u8; Self::SIZE] { - unsafe { core::mem::transmute(*self) } - } - - pub fn from_bytes(bytes: &[u8; Self::SIZE]) -> Self { - unsafe { core::mem::transmute(*bytes) } - } - } - }; -} -impl_bytes!(SdpcmHeader); -impl_bytes!(CdcHeader); -impl_bytes!(BdcHeader); impl_bytes!(DownloadHeader); + +pub const DOWNLOAD_FLAG_NO_CRC: u16 = 0x0001; +pub const DOWNLOAD_FLAG_BEGIN: u16 = 0x0002; +pub const DOWNLOAD_FLAG_END: u16 = 0x0004; +pub const DOWNLOAD_FLAG_HANDLER_VER: u16 = 0x1000; + +pub const DOWNLOAD_TYPE_CLM: u16 = 2; + +#[derive(Clone, Copy)] +#[repr(C)] +pub struct CountryInfo { + pub country_abbrev: [u8; 4], + pub rev: i32, + pub country_code: [u8; 4], +} +impl_bytes!(CountryInfo); From 30b7800f9ae0a7f26e292dbe55cc67fbe2d1b131 Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Mon, 11 Jul 2022 05:19:31 +0200 Subject: [PATCH 004/178] add event printing, add join but not working yet. --- Cargo.toml | 1 + examples/rpi-pico-w/src/main.rs | 3 + src/events.rs | 281 ++++++++++++++++++++++++++++++++ src/lib.rs | 80 ++++++++- src/structs.rs | 55 ++++++- 5 files changed, 414 insertions(+), 6 deletions(-) create mode 100644 src/events.rs diff --git a/Cargo.toml b/Cargo.toml index d1672146..bd27a48b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,3 +20,4 @@ futures = { version = "0.3.17", default-features = false, features = ["async-awa embedded-hal-1 = { package = "embedded-hal", version = "1.0.0-alpha.8" } embedded-hal-async = { version = "0.1.0-alpha.1" } +num_enum = { version = "0.5.7", default-features = false } diff --git a/examples/rpi-pico-w/src/main.rs b/examples/rpi-pico-w/src/main.rs index 7545dfde..d4aae847 100644 --- a/examples/rpi-pico-w/src/main.rs +++ b/examples/rpi-pico-w/src/main.rs @@ -45,4 +45,7 @@ async fn main(spawner: Spawner, p: Peripherals) { spawner.spawn(wifi_task(runner)).unwrap(); control.init().await; + + let ssid = "MikroTik-951589"; + control.join(ssid).await; } diff --git a/src/events.rs b/src/events.rs new file mode 100644 index 00000000..b35b12fa --- /dev/null +++ b/src/events.rs @@ -0,0 +1,281 @@ +#![allow(unused)] + +use core::num; + +#[derive(Clone, Copy, PartialEq, Eq, num_enum::FromPrimitive)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[repr(u8)] +pub enum Event { + #[num_enum(default)] + Unknown = 0xFF, + /// indicates status of set SSID + SET_SSID = 0, + /// differentiates join IBSS from found (START) IBSS + JOIN = 1, + /// STA founded an IBSS or AP started a BSS + START = 2, + /// 802.11 AUTH request + AUTH = 3, + /// 802.11 AUTH indication + AUTH_IND = 4, + /// 802.11 DEAUTH request + DEAUTH = 5, + /// 802.11 DEAUTH indication + DEAUTH_IND = 6, + /// 802.11 ASSOC request + ASSOC = 7, + /// 802.11 ASSOC indication + ASSOC_IND = 8, + /// 802.11 REASSOC request + REASSOC = 9, + /// 802.11 REASSOC indication + REASSOC_IND = 10, + /// 802.11 DISASSOC request + DISASSOC = 11, + /// 802.11 DISASSOC indication + DISASSOC_IND = 12, + /// 802.11h Quiet period started + QUIET_START = 13, + /// 802.11h Quiet period ended + QUIET_END = 14, + /// BEACONS received/lost indication + BEACON_RX = 15, + /// generic link indication + LINK = 16, + /// TKIP MIC error occurred + MIC_ERROR = 17, + /// NDIS style link indication + NDIS_LINK = 18, + /// roam attempt occurred: indicate status & reason + ROAM = 19, + /// change in dot11FailedCount (txfail) + TXFAIL = 20, + /// WPA2 pmkid cache indication + PMKID_CACHE = 21, + /// current AP's TSF value went backward + RETROGRADE_TSF = 22, + /// AP was pruned from join list for reason + PRUNE = 23, + /// report AutoAuth table entry match for join attempt + AUTOAUTH = 24, + /// Event encapsulating an EAPOL message + EAPOL_MSG = 25, + /// Scan results are ready or scan was aborted + SCAN_COMPLETE = 26, + /// indicate to host addts fail/success + ADDTS_IND = 27, + /// indicate to host delts fail/success + DELTS_IND = 28, + /// indicate to host of beacon transmit + BCNSENT_IND = 29, + /// Send the received beacon up to the host + BCNRX_MSG = 30, + /// indicate to host loss of beacon + BCNLOST_MSG = 31, + /// before attempting to roam + ROAM_PREP = 32, + /// PFN network found event + PFN_NET_FOUND = 33, + /// PFN network lost event + PFN_NET_LOST = 34, + RESET_COMPLETE = 35, + JOIN_START = 36, + ROAM_START = 37, + ASSOC_START = 38, + IBSS_ASSOC = 39, + RADIO = 40, + /// PSM microcode watchdog fired + PSM_WATCHDOG = 41, + /// CCX association start + CCX_ASSOC_START = 42, + /// CCX association abort + CCX_ASSOC_ABORT = 43, + /// probe request received + PROBREQ_MSG = 44, + SCAN_CONFIRM_IND = 45, + /// WPA Handshake + PSK_SUP = 46, + COUNTRY_CODE_CHANGED = 47, + /// WMMAC excedded medium time + EXCEEDED_MEDIUM_TIME = 48, + /// WEP ICV error occurred + ICV_ERROR = 49, + /// Unsupported unicast encrypted frame + UNICAST_DECODE_ERROR = 50, + /// Unsupported multicast encrypted frame + MULTICAST_DECODE_ERROR = 51, + TRACE = 52, + /// BT-AMP HCI event + BTA_HCI_EVENT = 53, + /// I/F change (for wlan host notification) + IF = 54, + /// P2P Discovery listen state expires + P2P_DISC_LISTEN_COMPLETE = 55, + /// indicate RSSI change based on configured levels + RSSI = 56, + /// PFN best network batching event + PFN_BEST_BATCHING = 57, + EXTLOG_MSG = 58, + /// Action frame reception + ACTION_FRAME = 59, + /// Action frame Tx complete + ACTION_FRAME_COMPLETE = 60, + /// assoc request received + PRE_ASSOC_IND = 61, + /// re-assoc request received + PRE_REASSOC_IND = 62, + /// channel adopted (xxx: obsoleted) + CHANNEL_ADOPTED = 63, + /// AP started + AP_STARTED = 64, + /// AP stopped due to DFS + DFS_AP_STOP = 65, + /// AP resumed due to DFS + DFS_AP_RESUME = 66, + /// WAI stations event + WAI_STA_EVENT = 67, + /// event encapsulating an WAI message + WAI_MSG = 68, + /// escan result event + ESCAN_RESULT = 69, + /// action frame off channel complete + ACTION_FRAME_OFF_CHAN_COMPLETE = 70, + /// probe response received + PROBRESP_MSG = 71, + /// P2P Probe request received + P2P_PROBREQ_MSG = 72, + DCS_REQUEST = 73, + /// credits for D11 FIFOs. [AC0,AC1,AC2,AC3,BC_MC,ATIM] + FIFO_CREDIT_MAP = 74, + /// Received action frame event WITH wl_event_rx_frame_data_t header + ACTION_FRAME_RX = 75, + /// Wake Event timer fired, used for wake WLAN test mode + WAKE_EVENT = 76, + /// Radio measurement complete + RM_COMPLETE = 77, + /// Synchronize TSF with the host + HTSFSYNC = 78, + /// request an overlay IOCTL/iovar from the host + OVERLAY_REQ = 79, + CSA_COMPLETE_IND = 80, + /// excess PM Wake Event to inform host + EXCESS_PM_WAKE_EVENT = 81, + /// no PFN networks around + PFN_SCAN_NONE = 82, + /// last found PFN network gets lost + PFN_SCAN_ALLGONE = 83, + GTK_PLUMBED = 84, + /// 802.11 ASSOC indication for NDIS only + ASSOC_IND_NDIS = 85, + /// 802.11 REASSOC indication for NDIS only + REASSOC_IND_NDIS = 86, + ASSOC_REQ_IE = 87, + ASSOC_RESP_IE = 88, + /// association recreated on resume + ASSOC_RECREATED = 89, + /// rx action frame event for NDIS only + ACTION_FRAME_RX_NDIS = 90, + /// authentication request received + AUTH_REQ = 91, + /// fast assoc recreation failed + SPEEDY_RECREATE_FAIL = 93, + /// port-specific event and payload (e.g. NDIS) + NATIVE = 94, + /// event for tx pkt delay suddently jump + PKTDELAY_IND = 95, + /// AWDL AW period starts + AWDL_AW = 96, + /// AWDL Master/Slave/NE master role event + AWDL_ROLE = 97, + /// Generic AWDL event + AWDL_EVENT = 98, + /// NIC AF txstatus + NIC_AF_TXS = 99, + /// NAN event + NAN = 100, + BEACON_FRAME_RX = 101, + /// desired service found + SERVICE_FOUND = 102, + /// GAS fragment received + GAS_FRAGMENT_RX = 103, + /// GAS sessions all complete + GAS_COMPLETE = 104, + /// New device found by p2p offload + P2PO_ADD_DEVICE = 105, + /// device has been removed by p2p offload + P2PO_DEL_DEVICE = 106, + /// WNM event to notify STA enter sleep mode + WNM_STA_SLEEP = 107, + /// Indication of MAC tx failures (exhaustion of 802.11 retries) exceeding threshold(s) + TXFAIL_THRESH = 108, + /// Proximity Detection event + PROXD = 109, + /// AWDL RX Probe response + AWDL_RX_PRB_RESP = 111, + /// AWDL RX Action Frames + AWDL_RX_ACT_FRAME = 112, + /// AWDL Wowl nulls + AWDL_WOWL_NULLPKT = 113, + /// AWDL Phycal status + AWDL_PHYCAL_STATUS = 114, + /// AWDL OOB AF status + AWDL_OOB_AF_STATUS = 115, + /// Interleaved Scan status + AWDL_SCAN_STATUS = 116, + /// AWDL AW Start + AWDL_AW_START = 117, + /// AWDL AW End + AWDL_AW_END = 118, + /// AWDL AW Extensions + AWDL_AW_EXT = 119, + AWDL_PEER_CACHE_CONTROL = 120, + CSA_START_IND = 121, + CSA_DONE_IND = 122, + CSA_FAILURE_IND = 123, + /// CCA based channel quality report + CCA_CHAN_QUAL = 124, + /// to report change in BSSID while roaming + BSSID = 125, + /// tx error indication + TX_STAT_ERROR = 126, + /// credit check for BCMC supported + BCMC_CREDIT_SUPPORT = 127, + /// psta primary interface indication + PSTA_PRIMARY_INTF_IND = 128, + /// Handover Request Initiated + BT_WIFI_HANDOVER_REQ = 130, + /// Southpaw TxInhibit notification + SPW_TXINHIBIT = 131, + /// FBT Authentication Request Indication + FBT_AUTH_REQ_IND = 132, + /// Enhancement addition for RSSI + RSSI_LQM = 133, + /// Full probe/beacon (IEs etc) results + PFN_GSCAN_FULL_RESULT = 134, + /// Significant change in rssi of bssids being tracked + PFN_SWC = 135, + /// a STA been authroized for traffic + AUTHORIZED = 136, + /// probe req with wl_event_rx_frame_data_t header + PROBREQ_MSG_RX = 137, + /// PFN completed scan of network list + PFN_SCAN_COMPLETE = 138, + /// RMC Event + RMC_EVENT = 139, + /// DPSTA interface indication + DPSTA_INTF_IND = 140, + /// RRM Event + RRM = 141, + /// ULP entry event + ULP = 146, + /// TCP Keep Alive Offload Event + TKO = 151, + /// authentication request received + EXT_AUTH_REQ = 187, + /// authentication request received + EXT_AUTH_FRAME_RX = 188, + /// mgmt frame Tx complete + MGMT_FRAME_TXSTATUS = 189, + /// highest val + 1 for range checking + LAST = 190, +} diff --git a/src/lib.rs b/src/lib.rs index 698c52f4..040caed8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,6 +7,7 @@ pub(crate) mod fmt; mod countries; +mod events; mod structs; use core::cell::Cell; @@ -253,9 +254,52 @@ impl<'a> Control<'a> { }; self.set_iovar("country", &country_info.to_bytes()).await; + // set country takes some time, next ioctls fail if we don't wait. + Timer::after(Duration::from_millis(100)).await; + + // self.set_iovar_u32("ampdu_ba_wsize", 8).await; + // self.set_iovar_u32("ampdu_mpdu", 4).await; + // self.set_iovar_u32("ampdu_rx_factor", 0).await; // this crashes + + Timer::after(Duration::from_millis(100)).await; + + // evts + let mut evts = EventMask { + iface: 0, + events: [0xFF; 24], + }; + self.set_iovar("bsscfg:event_msgs", &evts.to_bytes()).await; + + // set wifi up + self.ioctl(2, 2, 0, &[]).await; + + Timer::after(Duration::from_millis(100)).await; + info!("INIT DONE"); } + pub async fn join(&mut self, ssid: &str) { + self.ioctl_set_u32(134, 0, 0).await; // wsec = open + self.set_iovar_u32x2("bsscfg:sup_wpa", 0, 0).await; + self.ioctl_set_u32(20, 0, 1).await; // set_infra = 1 + self.ioctl_set_u32(22, 0, 0).await; // set_auth = open (0) + + let mut i = SsidInfo { + len: ssid.len() as _, + ssid: [0; 32], + }; + i.ssid[..ssid.len()].copy_from_slice(ssid.as_bytes()); + self.ioctl(2, 26, 0, &i.to_bytes()).await; // set_ssid + + info!("JOINED"); + } + + async fn set_iovar_u32x2(&mut self, name: &str, val1: u32, val2: u32) { + let mut buf = [0; 8]; + buf[0..4].copy_from_slice(&val1.to_le_bytes()); + buf[4..8].copy_from_slice(&val2.to_le_bytes()); + self.set_iovar(name, &buf).await + } async fn set_iovar_u32(&mut self, name: &str, val: u32) { self.set_iovar(name, &val.to_le_bytes()).await } @@ -272,6 +316,10 @@ impl<'a> Control<'a> { self.ioctl(2, 263, 0, &buf[..total_len]).await; } + async fn ioctl_set_u32(&mut self, cmd: u32, iface: u32, val: u32) { + self.ioctl(2, cmd, 0, &val.to_le_bytes()).await + } + async fn ioctl(&mut self, kind: u32, cmd: u32, iface: u32, buf: &[u8]) { // TODO cancel ioctl on future drop. @@ -488,7 +536,7 @@ impl<'a, PWR: Pin, CS: Pin, CLK: Pin, DIO: Pin> Runner<'a, PWR, CS, CLK, DIO> { } self.cs.set_high(); - info!("rx {:02x}", &buf[..(len as usize).min(48)]); + //info!("rx {:02x}", &buf[..(len as usize).min(36)]); self.rx(&buf[..len as usize]); } @@ -528,14 +576,38 @@ impl<'a, PWR: Pin, CS: Pin, CLK: Pin, DIO: Pin> Runner<'a, PWR, CS, CLK, DIO> { return; } - let cdc_header = - CdcHeader::from_bytes(payload[..CdcHeader::SIZE].try_into().unwrap()); + let cdc_header = CdcHeader::from_bytes(payload[..CdcHeader::SIZE].try_into().unwrap()); if cdc_header.id == self.state.ioctl_id.get() { assert_eq!(cdc_header.status, 0); // todo propagate error self.state.ioctl_state.set(IoctlState::Done); } } + 1 => { + let bcd_header = BcdHeader::from_bytes(&payload[..BcdHeader::SIZE].try_into().unwrap()); + //info!("{}", bcd_header); + + let packet_start = BcdHeader::SIZE + 4 * bcd_header.data_offset as usize; + if packet_start > payload.len() { + warn!("packet start out of range."); + return; + } + let packet = &payload[packet_start..]; + //info!("rx {:02x}", &packet[..(packet.len() as usize).min(36)]); + + let evt = EventHeader::from_bytes(&packet[24..][..EventHeader::SIZE].try_into().unwrap()); + let evt_num = evt.event_type.to_be() as u8; + let evt_data_len = evt.datalen.to_be() as u8; + let evt_data = &packet[24 + EventHeader::SIZE..][..evt_data_len as usize]; + info!( + "=== EVENT {} ({}) {} {:02x}", + events::Event::from(evt_num), + evt_num, + evt, + evt_data + ); + } + _ => {} } } @@ -575,7 +647,7 @@ impl<'a, PWR: Pin, CS: Pin, CLK: Pin, DIO: Pin> Runner<'a, PWR, CS, CLK, DIO> { let total_len = (total_len + 3) & !3; // round up to 4byte - info!("tx {:02x}", &buf[..total_len.min(48)]); + //info!("tx {:02x}", &buf[..total_len.min(48)]); let cmd = cmd_word(true, true, FUNC_WLAN, 0, total_len as _); self.cs.set_low(); diff --git a/src/structs.rs b/src/structs.rs index bce9ab9f..8a98d522 100644 --- a/src/structs.rs +++ b/src/structs.rs @@ -15,6 +15,7 @@ macro_rules! impl_bytes { } #[derive(Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] #[repr(C)] pub struct SdpcmHeader { pub len: u16, @@ -37,6 +38,7 @@ pub struct SdpcmHeader { impl_bytes!(SdpcmHeader); #[derive(Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] #[repr(C)] pub struct CdcHeader { pub cmd: u32, @@ -49,8 +51,9 @@ pub struct CdcHeader { impl_bytes!(CdcHeader); #[derive(Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] #[repr(C)] -pub struct BdcHeader { +pub struct BcdHeader { pub flags: u8, /// 802.1d Priority (low 3 bits) pub priority: u8, @@ -58,7 +61,36 @@ pub struct BdcHeader { /// Offset from end of BDC header to packet data, in 4-uint8_t words. Leaves room for optional headers. pub data_offset: u8, } -impl_bytes!(BdcHeader); +impl_bytes!(BcdHeader); + +#[derive(Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[repr(C)] +pub struct EventHeader { + /// version + pub version: u16, + /// see flags below + pub flags: u16, + /// Message (see below) + pub event_type: u32, + /// Status code (see below) + pub status: u32, + /// Reason code (if applicable) + pub reason: u32, + /// WLC_E_AUTH + pub auth_type: u32, + /// data buf + pub datalen: u32, + /// Station address (if applicable) + pub addr: [u8; 6], + /// name of the incoming packet interface + pub ifname: [u8; 16], + /// destination OS i/f index + pub ifidx: u8, + /// source bsscfg index + pub bsscfgidx: u8, +} +impl_bytes!(EventHeader); #[derive(Clone, Copy)] #[repr(C)] @@ -78,6 +110,7 @@ pub const DOWNLOAD_FLAG_HANDLER_VER: u16 = 0x1000; pub const DOWNLOAD_TYPE_CLM: u16 = 2; #[derive(Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] #[repr(C)] pub struct CountryInfo { pub country_abbrev: [u8; 4], @@ -85,3 +118,21 @@ pub struct CountryInfo { pub country_code: [u8; 4], } impl_bytes!(CountryInfo); + +#[derive(Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[repr(C)] +pub struct SsidInfo { + pub len: u32, + pub ssid: [u8; 32], +} +impl_bytes!(SsidInfo); + +#[derive(Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[repr(C)] +pub struct EventMask { + pub iface: u32, + pub events: [u8; 24], +} +impl_bytes!(EventMask); From 3ffdbd2ca3ed7cc3da95c391f1928342afb55e10 Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Mon, 11 Jul 2022 22:44:42 +0200 Subject: [PATCH 005/178] stuff --- examples/rpi-pico-w/src/main.rs | 1 + src/lib.rs | 32 ++++++++++++++++++++------------ src/structs.rs | 12 ++++++++++++ 3 files changed, 33 insertions(+), 12 deletions(-) diff --git a/examples/rpi-pico-w/src/main.rs b/examples/rpi-pico-w/src/main.rs index d4aae847..bef820d3 100644 --- a/examples/rpi-pico-w/src/main.rs +++ b/examples/rpi-pico-w/src/main.rs @@ -6,6 +6,7 @@ use core::slice; use defmt::{assert, assert_eq, panic, *}; use embassy::executor::Spawner; +use embassy::time::{Duration, Timer}; use embassy::util::Forever; use embassy_rp::gpio::{Flex, Level, Output, Pin}; use embassy_rp::peripherals::{PIN_23, PIN_24, PIN_25, PIN_29}; diff --git a/src/lib.rs b/src/lib.rs index 040caed8..27191797 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -243,7 +243,7 @@ impl<'a> Control<'a> { info!("Configuring misc stuff..."); self.set_iovar_u32("bus:txglom", 0).await; - self.set_iovar_u32("apsta", 1).await; + //self.set_iovar_u32("apsta", 1).await; self.set_iovar("cur_etheraddr", &[02, 03, 04, 05, 06, 07]).await; let country = countries::WORLD_WIDE_XX; @@ -257,9 +257,13 @@ impl<'a> Control<'a> { // set country takes some time, next ioctls fail if we don't wait. Timer::after(Duration::from_millis(100)).await; - // self.set_iovar_u32("ampdu_ba_wsize", 8).await; - // self.set_iovar_u32("ampdu_mpdu", 4).await; - // self.set_iovar_u32("ampdu_rx_factor", 0).await; // this crashes + self.ioctl_set_u32(64, 0, 0).await; // WLC_SET_ANTDIV + + self.set_iovar_u32("bus:txglom", 0).await; + //self.set_iovar_u32("apsta", 1).await; + self.set_iovar_u32("ampdu_ba_wsize", 8).await; + self.set_iovar_u32("ampdu_mpdu", 4).await; + //self.set_iovar_u32("ampdu_rx_factor", 0).await; // this crashes Timer::after(Duration::from_millis(100)).await; @@ -275,6 +279,12 @@ impl<'a> Control<'a> { Timer::after(Duration::from_millis(100)).await; + self.ioctl_set_u32(86, 0, 0).await; // no power save + self.ioctl_set_u32(110, 0, 1).await; // SET_GMODE = auto + self.ioctl_set_u32(142, 0, 0).await; // SET_BAND = any + + Timer::after(Duration::from_millis(100)).await; + info!("INIT DONE"); } @@ -494,11 +504,11 @@ impl<'a, PWR: Pin, CS: Pin, CLK: Pin, DIO: Pin> Runner<'a, PWR, CS, CLK, DIO> { let mut val = self.read8(FUNC_BACKPLANE, REG_BACKPLANE_SLEEP_CSR); val |= 0x01; // SBSDIO_SLPCSR_KEEP_SDIO_ON self.write8(FUNC_BACKPLANE, REG_BACKPLANE_SLEEP_CSR, val); + */ // clear pulls self.write8(FUNC_BACKPLANE, REG_BACKPLANE_PULL_UP, 0); let _ = self.read8(FUNC_BACKPLANE, REG_BACKPLANE_PULL_UP); - */ info!("init done "); } @@ -595,14 +605,12 @@ impl<'a, PWR: Pin, CS: Pin, CLK: Pin, DIO: Pin> Runner<'a, PWR, CS, CLK, DIO> { let packet = &payload[packet_start..]; //info!("rx {:02x}", &packet[..(packet.len() as usize).min(36)]); - let evt = EventHeader::from_bytes(&packet[24..][..EventHeader::SIZE].try_into().unwrap()); - let evt_num = evt.event_type.to_be() as u8; - let evt_data_len = evt.datalen.to_be() as u8; - let evt_data = &packet[24 + EventHeader::SIZE..][..evt_data_len as usize]; + let mut evt = EventHeader::from_bytes(&packet[24..][..EventHeader::SIZE].try_into().unwrap()); + evt.byteswap(); + let evt_data = &packet[24 + EventHeader::SIZE..][..evt.datalen as usize]; info!( - "=== EVENT {} ({}) {} {:02x}", - events::Event::from(evt_num), - evt_num, + "=== EVENT {}: {} {:02x}", + events::Event::from(evt.event_type as u8), evt, evt_data ); diff --git a/src/structs.rs b/src/structs.rs index 8a98d522..beda9f36 100644 --- a/src/structs.rs +++ b/src/structs.rs @@ -92,6 +92,18 @@ pub struct EventHeader { } impl_bytes!(EventHeader); +impl EventHeader { + pub fn byteswap(&mut self) { + self.version = self.version.to_be(); + self.flags = self.flags.to_be(); + self.event_type = self.event_type.to_be(); + self.status = self.status.to_be(); + self.reason = self.reason.to_be(); + self.auth_type = self.auth_type.to_be(); + self.datalen = self.datalen.to_be(); + } +} + #[derive(Clone, Copy)] #[repr(C)] pub struct DownloadHeader { From d96ad248b33d8ca89daf88c5878cb24d6566cc6d Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Mon, 11 Jul 2022 22:53:57 +0200 Subject: [PATCH 006/178] Add LICENSEs --- LICENSE-APACHE | 201 +++++++++++++++++++++++++++++++++++++++++++++++++ LICENSE-MIT | 25 ++++++ README.md | 14 ++++ 3 files changed, 240 insertions(+) create mode 100644 LICENSE-APACHE create mode 100644 LICENSE-MIT create mode 100644 README.md diff --git a/LICENSE-APACHE b/LICENSE-APACHE new file mode 100644 index 00000000..ea4fa15c --- /dev/null +++ b/LICENSE-APACHE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright 2019-2022 Embassy project contributors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/LICENSE-MIT b/LICENSE-MIT new file mode 100644 index 00000000..87c05283 --- /dev/null +++ b/LICENSE-MIT @@ -0,0 +1,25 @@ +Copyright (c) 2019-2022 Embassy project contributors + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 00000000..7df988b7 --- /dev/null +++ b/README.md @@ -0,0 +1,14 @@ +# cyw43 + +Very WIP driver for the CYW43439 wifi chip, used in the Raspberry Pi Pico W. + +## License + +This work is licensed under either of + +- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or + ) +- MIT license ([LICENSE-MIT](LICENSE-MIT) or ) + +at your option. + From 18b11e7417e1338fb18e6ceda609f2a0841d7a57 Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Tue, 12 Jul 2022 03:34:27 +0200 Subject: [PATCH 007/178] check clmload_status. --- src/lib.rs | 85 +++++++++++++++++++++++++++++++++++++------------- src/structs.rs | 3 +- 2 files changed, 64 insertions(+), 24 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 27191797..6a1b7970 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -184,10 +184,14 @@ enum IoctlState { kind: u32, cmd: u32, iface: u32, - buf: *const [u8], + buf: *mut [u8], + }, + Sent { + buf: *mut [u8], + }, + Done { + resp_len: usize, }, - Sent, - Done, } pub struct State { @@ -237,9 +241,12 @@ impl<'a> Control<'a> { buf[0..8].copy_from_slice(b"clmload\x00"); buf[8..20].copy_from_slice(&header.to_bytes()); buf[20..][..chunk.len()].copy_from_slice(&chunk); - self.ioctl(2, 263, 0, &buf[..8 + 12 + chunk.len()]).await; + self.ioctl(2, 263, 0, &mut buf[..8 + 12 + chunk.len()]).await; } + // check clmload ok + assert_eq!(self.get_iovar_u32("clmload_status").await, 0); + info!("Configuring misc stuff..."); self.set_iovar_u32("bus:txglom", 0).await; @@ -275,7 +282,7 @@ impl<'a> Control<'a> { self.set_iovar("bsscfg:event_msgs", &evts.to_bytes()).await; // set wifi up - self.ioctl(2, 2, 0, &[]).await; + self.ioctl(2, 2, 0, &mut []).await; Timer::after(Duration::from_millis(100)).await; @@ -299,7 +306,7 @@ impl<'a> Control<'a> { ssid: [0; 32], }; i.ssid[..ssid.len()].copy_from_slice(ssid.as_bytes()); - self.ioctl(2, 26, 0, &i.to_bytes()).await; // set_ssid + self.ioctl(2, 26, 0, &mut i.to_bytes()).await; // set_ssid info!("JOINED"); } @@ -310,10 +317,18 @@ impl<'a> Control<'a> { buf[4..8].copy_from_slice(&val2.to_le_bytes()); self.set_iovar(name, &buf).await } + async fn set_iovar_u32(&mut self, name: &str, val: u32) { self.set_iovar(name, &val.to_le_bytes()).await } + async fn get_iovar_u32(&mut self, name: &str) -> u32 { + let mut buf = [0; 4]; + let len = self.get_iovar(name, &mut buf).await; + assert_eq!(len, 4); + u32::from_le_bytes(buf) + } + async fn set_iovar(&mut self, name: &str, val: &[u8]) { info!("set {} = {:02x}", name, val); @@ -323,14 +338,28 @@ impl<'a> Control<'a> { buf[name.len() + 1..][..val.len()].copy_from_slice(val); let total_len = name.len() + 1 + val.len(); - self.ioctl(2, 263, 0, &buf[..total_len]).await; + self.ioctl(2, 263, 0, &mut buf).await; + } + + async fn get_iovar(&mut self, name: &str, res: &mut [u8]) -> usize { + info!("get {}", name); + + let mut buf = [0; 64]; + buf[..name.len()].copy_from_slice(name.as_bytes()); + buf[name.len()] = 0; + + let total_len = name.len() + 1 + res.len(); + let res_len = self.ioctl(2, 262, 0, &mut buf[..total_len]).await - name.len() - 1; + res[..res_len].copy_from_slice(&buf[name.len() + 1..][..res_len]); + res_len } async fn ioctl_set_u32(&mut self, cmd: u32, iface: u32, val: u32) { - self.ioctl(2, cmd, 0, &val.to_le_bytes()).await + let mut buf = val.to_le_bytes(); + self.ioctl(2, cmd, 0, &mut buf).await; } - async fn ioctl(&mut self, kind: u32, cmd: u32, iface: u32, buf: &[u8]) { + async fn ioctl(&mut self, kind: u32, cmd: u32, iface: u32, buf: &mut [u8]) -> usize { // TODO cancel ioctl on future drop. while !matches!(self.state.ioctl_state.get(), IoctlState::Idle) { @@ -343,11 +372,16 @@ impl<'a> Control<'a> { .ioctl_state .set(IoctlState::Pending { kind, cmd, iface, buf }); - while !matches!(self.state.ioctl_state.get(), IoctlState::Done) { + let resp_len = loop { + if let IoctlState::Done { resp_len } = self.state.ioctl_state.get() { + break resp_len; + } yield_now().await; - } + }; self.state.ioctl_state.set(IoctlState::Idle); + + resp_len } } @@ -519,7 +553,7 @@ impl<'a, PWR: Pin, CS: Pin, CLK: Pin, DIO: Pin> Runner<'a, PWR, CS, CLK, DIO> { // Send stuff if let IoctlState::Pending { kind, cmd, iface, buf } = self.state.ioctl_state.get() { self.send_ioctl(kind, cmd, iface, unsafe { &*buf }, self.state.ioctl_id.get()); - self.state.ioctl_state.set(IoctlState::Sent); + self.state.ioctl_state.set(IoctlState::Sent { buf }); } // Receive stuff @@ -546,7 +580,7 @@ impl<'a, PWR: Pin, CS: Pin, CLK: Pin, DIO: Pin> Runner<'a, PWR, CS, CLK, DIO> { } self.cs.set_high(); - //info!("rx {:02x}", &buf[..(len as usize).min(36)]); + trace!("rx {:02x}", &buf[..(len as usize).min(48)]); self.rx(&buf[..len as usize]); } @@ -564,7 +598,7 @@ impl<'a, PWR: Pin, CS: Pin, CLK: Pin, DIO: Pin> Runner<'a, PWR, CS, CLK, DIO> { } let sdpcm_header = SdpcmHeader::from_bytes(packet[..SdpcmHeader::SIZE].try_into().unwrap()); - + trace!("rx {:?}", sdpcm_header); if sdpcm_header.len != !sdpcm_header.len_inv { warn!("len inv mismatch"); return; @@ -587,15 +621,21 @@ impl<'a, PWR: Pin, CS: Pin, CLK: Pin, DIO: Pin> Runner<'a, PWR, CS, CLK, DIO> { } let cdc_header = CdcHeader::from_bytes(payload[..CdcHeader::SIZE].try_into().unwrap()); + trace!(" {:?}", cdc_header); - if cdc_header.id == self.state.ioctl_id.get() { - assert_eq!(cdc_header.status, 0); // todo propagate error - self.state.ioctl_state.set(IoctlState::Done); + if let IoctlState::Sent { buf } = self.state.ioctl_state.get() { + if cdc_header.id == self.state.ioctl_id.get() { + assert_eq!(cdc_header.status, 0); // todo propagate error instead + + let resp_len = cdc_header.len as usize; + (unsafe { &mut *buf }[..resp_len]).copy_from_slice(&payload[CdcHeader::SIZE..][..resp_len]); + self.state.ioctl_state.set(IoctlState::Done { resp_len }); + } } } 1 => { let bcd_header = BcdHeader::from_bytes(&payload[..BcdHeader::SIZE].try_into().unwrap()); - //info!("{}", bcd_header); + trace!(" {:?}", bcd_header); let packet_start = BcdHeader::SIZE + 4 * bcd_header.data_offset as usize; if packet_start > payload.len() { @@ -603,7 +643,7 @@ impl<'a, PWR: Pin, CS: Pin, CLK: Pin, DIO: Pin> Runner<'a, PWR, CS, CLK, DIO> { return; } let packet = &payload[packet_start..]; - //info!("rx {:02x}", &packet[..(packet.len() as usize).min(36)]); + trace!(" {:02x}", &packet[..(packet.len() as usize).min(36)]); let mut evt = EventHeader::from_bytes(&packet[24..][..EventHeader::SIZE].try_into().unwrap()); evt.byteswap(); @@ -642,12 +682,13 @@ impl<'a, PWR: Pin, CS: Pin, CLK: Pin, DIO: Pin> Runner<'a, PWR, CS, CLK, DIO> { let cdc_header = CdcHeader { cmd: cmd, - out_len: data.len() as _, - in_len: 0, + len: data.len() as _, flags: kind as u16 | (iface as u16) << 12, id, status: 0, }; + trace!("tx {:?}", sdpcm_header); + trace!(" {:?}", cdc_header); buf[0..SdpcmHeader::SIZE].copy_from_slice(&sdpcm_header.to_bytes()); buf[SdpcmHeader::SIZE..][..CdcHeader::SIZE].copy_from_slice(&cdc_header.to_bytes()); @@ -655,7 +696,7 @@ impl<'a, PWR: Pin, CS: Pin, CLK: Pin, DIO: Pin> Runner<'a, PWR, CS, CLK, DIO> { let total_len = (total_len + 3) & !3; // round up to 4byte - //info!("tx {:02x}", &buf[..total_len.min(48)]); + trace!(" {:02x}", &buf[..total_len.min(48)]); let cmd = cmd_word(true, true, FUNC_WLAN, 0, total_len as _); self.cs.set_low(); diff --git a/src/structs.rs b/src/structs.rs index beda9f36..91df616a 100644 --- a/src/structs.rs +++ b/src/structs.rs @@ -42,8 +42,7 @@ impl_bytes!(SdpcmHeader); #[repr(C)] pub struct CdcHeader { pub cmd: u32, - pub out_len: u16, - pub in_len: u16, + pub len: u32, pub flags: u16, pub id: u16, pub status: u32, From e1fd7dfc40bbb1ccbab511fb1e0d7a1120ae68a0 Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Tue, 12 Jul 2022 04:17:07 +0200 Subject: [PATCH 008/178] wpa2 join... still nothing. --- examples/rpi-pico-w/src/main.rs | 4 +-- src/lib.rs | 59 ++++++++++++++++++++++++++++++--- src/structs.rs | 10 ++++++ 3 files changed, 66 insertions(+), 7 deletions(-) diff --git a/examples/rpi-pico-w/src/main.rs b/examples/rpi-pico-w/src/main.rs index bef820d3..6d161414 100644 --- a/examples/rpi-pico-w/src/main.rs +++ b/examples/rpi-pico-w/src/main.rs @@ -47,6 +47,6 @@ async fn main(spawner: Spawner, p: Peripherals) { control.init().await; - let ssid = "MikroTik-951589"; - control.join(ssid).await; + //control.join_open("MikroTik-951589").await; + control.join_wpa2("MikroTik-951589", "fasdfasdfasdf").await; } diff --git a/src/lib.rs b/src/lib.rs index 6a1b7970..70fa7ede 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -250,8 +250,8 @@ impl<'a> Control<'a> { info!("Configuring misc stuff..."); self.set_iovar_u32("bus:txglom", 0).await; - //self.set_iovar_u32("apsta", 1).await; - self.set_iovar("cur_etheraddr", &[02, 03, 04, 05, 06, 07]).await; + self.set_iovar_u32("apsta", 1).await; + //self.set_iovar("cur_etheraddr", &[02, 03, 04, 05, 06, 07]).await; let country = countries::WORLD_WIDE_XX; let country_info = CountryInfo { @@ -267,9 +267,13 @@ impl<'a> Control<'a> { self.ioctl_set_u32(64, 0, 0).await; // WLC_SET_ANTDIV self.set_iovar_u32("bus:txglom", 0).await; - //self.set_iovar_u32("apsta", 1).await; + Timer::after(Duration::from_millis(100)).await; + //self.set_iovar_u32("apsta", 1).await; // this crashes, also we already did it before...?? + Timer::after(Duration::from_millis(100)).await; self.set_iovar_u32("ampdu_ba_wsize", 8).await; + Timer::after(Duration::from_millis(100)).await; self.set_iovar_u32("ampdu_mpdu", 4).await; + Timer::after(Duration::from_millis(100)).await; //self.set_iovar_u32("ampdu_rx_factor", 0).await; // this crashes Timer::after(Duration::from_millis(100)).await; @@ -281,12 +285,20 @@ impl<'a> Control<'a> { }; self.set_iovar("bsscfg:event_msgs", &evts.to_bytes()).await; + Timer::after(Duration::from_millis(100)).await; + // set wifi up self.ioctl(2, 2, 0, &mut []).await; Timer::after(Duration::from_millis(100)).await; - self.ioctl_set_u32(86, 0, 0).await; // no power save + // power save mode 2 + self.set_iovar_u32("pm2_sleep_ret", 0xc8).await; + self.set_iovar_u32("bcn_li_bcn", 1).await; + self.set_iovar_u32("bcn_li_dtim", 1).await; + self.set_iovar_u32("assoc_listen", 10).await; + self.ioctl_set_u32(0x86, 0, 2).await; + self.ioctl_set_u32(110, 0, 1).await; // SET_GMODE = auto self.ioctl_set_u32(142, 0, 0).await; // SET_BAND = any @@ -295,7 +307,9 @@ impl<'a> Control<'a> { info!("INIT DONE"); } - pub async fn join(&mut self, ssid: &str) { + pub async fn join_open(&mut self, ssid: &str) { + self.set_iovar_u32("ampdu_ba_wsize", 8).await; + self.ioctl_set_u32(134, 0, 0).await; // wsec = open self.set_iovar_u32x2("bsscfg:sup_wpa", 0, 0).await; self.ioctl_set_u32(20, 0, 1).await; // set_infra = 1 @@ -311,6 +325,38 @@ impl<'a> Control<'a> { info!("JOINED"); } + pub async fn join_wpa2(&mut self, ssid: &str, passphrase: &str) { + self.set_iovar_u32("ampdu_ba_wsize", 8).await; + + self.ioctl_set_u32(134, 0, 4).await; // wsec = wpa2 + self.set_iovar_u32x2("bsscfg:sup_wpa", 0, 1).await; + self.set_iovar_u32x2("bsscfg:sup_wpa2_eapver", 0, 0xFFFF_FFFF).await; + self.set_iovar_u32x2("bsscfg:sup_wpa_tmo", 0, 2500).await; + + Timer::after(Duration::from_millis(100)).await; + + let mut pfi = PassphraseInfo { + len: passphrase.len() as _, + flags: 1, + passphrase: [0; 64], + }; + pfi.passphrase[..passphrase.len()].copy_from_slice(passphrase.as_bytes()); + self.ioctl(2, 268, 0, &mut pfi.to_bytes()).await; // WLC_SET_WSEC_PMK + + self.ioctl_set_u32(20, 0, 1).await; // set_infra = 1 + self.ioctl_set_u32(22, 0, 0).await; // set_auth = 0 (open) + self.ioctl_set_u32(165, 0, 0x80).await; // set_wpa_auth + + let mut i = SsidInfo { + len: ssid.len() as _, + ssid: [0; 32], + }; + i.ssid[..ssid.len()].copy_from_slice(ssid.as_bytes()); + self.ioctl(2, 26, 0, &mut i.to_bytes()).await; // set_ssid + + info!("JOINED"); + } + async fn set_iovar_u32x2(&mut self, name: &str, val1: u32, val2: u32) { let mut buf = [0; 8]; buf[0..4].copy_from_slice(&val1.to_le_bytes()); @@ -362,6 +408,9 @@ impl<'a> Control<'a> { async fn ioctl(&mut self, kind: u32, cmd: u32, iface: u32, buf: &mut [u8]) -> usize { // TODO cancel ioctl on future drop. + // snail mode 🐌 + Timer::after(Duration::from_millis(100)).await; + while !matches!(self.state.ioctl_state.get(), IoctlState::Idle) { yield_now().await; } diff --git a/src/structs.rs b/src/structs.rs index 91df616a..dd2c0cfe 100644 --- a/src/structs.rs +++ b/src/structs.rs @@ -139,6 +139,16 @@ pub struct SsidInfo { } impl_bytes!(SsidInfo); +#[derive(Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[repr(C)] +pub struct PassphraseInfo { + pub len: u16, + pub flags: u16, + pub passphrase: [u8; 64], +} +impl_bytes!(PassphraseInfo); + #[derive(Clone, Copy)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] #[repr(C)] From f60407feb3c2b2f3a364bd075dee2840220a5314 Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Tue, 12 Jul 2022 05:06:29 +0200 Subject: [PATCH 009/178] ITS DOING SOMETHING --- src/lib.rs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 70fa7ede..e0c2c931 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -593,6 +593,12 @@ impl<'a, PWR: Pin, CS: Pin, CLK: Pin, DIO: Pin> Runner<'a, PWR, CS, CLK, DIO> { self.write8(FUNC_BACKPLANE, REG_BACKPLANE_PULL_UP, 0); let _ = self.read8(FUNC_BACKPLANE, REG_BACKPLANE_PULL_UP); + // start HT clock + //self.write8(FUNC_BACKPLANE, REG_BACKPLANE_CHIP_CLOCK_CSR, 0x10); + //info!("waiting for HT clock..."); + //while self.read8(FUNC_BACKPLANE, REG_BACKPLANE_CHIP_CLOCK_CSR) & 0x80 == 0 {} + //info!("clock ok"); + info!("init done "); } @@ -1084,18 +1090,18 @@ static NVRAM: &'static [u8] = &*nvram!( b"boardtype=0x0887", b"boardrev=0x1100", b"boardnum=22", - b"macaddr=00:A0:50:86:aa:b6", + b"macaddr=00:A0:50:b5:59:5e", b"sromrev=11", b"boardflags=0x00404001", b"boardflags3=0x04000000", - b"xtalfreq=26000", + b"xtalfreq=37400", b"nocrc=1", b"ag0=255", b"aa2g=1", b"ccode=ALL", b"pa0itssit=0x20", b"extpagain2g=0", - b"pa2ga0=-168,7161,-820", + b"pa2ga0=-168,6649,-778", b"AvVmid_c0=0x0,0xc8", b"cckpwroffset0=5", b"maxp2ga0=84", @@ -1118,7 +1124,7 @@ static NVRAM: &'static [u8] = &*nvram!( b"il0macaddr=00:90:4c:c5:12:38", b"wl0id=0x431b", b"deadman_to=0xffffffff", - b"muxenab=0x1", + b"muxenab=0x100", b"spurconfig=0x3", b"glitch_based_crsmin=1", b"btc_mode=1", From ce7353fba4e2a133087f0312ae47184aa180642e Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Tue, 12 Jul 2022 07:52:16 +0200 Subject: [PATCH 010/178] Hook up embassy-net. IT WORKS. --- Cargo.toml | 1 + examples/rpi-pico-w/Cargo.toml | 9 ++ examples/rpi-pico-w/src/main.rs | 78 ++++++++++++++- src/lib.rs | 168 ++++++++++++++++++++++++++++++-- src/structs.rs | 9 ++ 5 files changed, 255 insertions(+), 10 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index bd27a48b..31a14f96 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,6 +9,7 @@ log = ["dep:log"] [dependencies] embassy = { version = "0.1.0" } embassy-rp = { version = "0.1.0", features = ["unstable-traits", "nightly", "unstable-pac"] } +embassy-net = { version = "0.1.0" } atomic-polyfill = "0.1.5" defmt = { version = "0.3", optional = true } diff --git a/examples/rpi-pico-w/Cargo.toml b/examples/rpi-pico-w/Cargo.toml index 8dbcb20d..9e1d7547 100644 --- a/examples/rpi-pico-w/Cargo.toml +++ b/examples/rpi-pico-w/Cargo.toml @@ -8,6 +8,7 @@ edition = "2021" cyw43 = { path = "../../", features = ["defmt"]} embassy = { version = "0.1.0", features = ["defmt", "defmt-timestamp-uptime"] } embassy-rp = { version = "0.1.0", features = ["defmt", "unstable-traits", "nightly", "unstable-pac"] } +embassy-net = { version = "0.1.0", features = ["defmt", "tcp", "dhcpv4", "medium-ethernet", "pool-16"] } atomic-polyfill = "0.1.5" defmt = "0.3" @@ -20,13 +21,21 @@ futures = { version = "0.3.17", default-features = false, features = ["async-awa embedded-hal-1 = { package = "embedded-hal", version = "1.0.0-alpha.8" } embedded-hal-async = { version = "0.1.0-alpha.1" } +embedded-io = { version = "0.3.0", features = ["async", "defmt"] } +heapless = "0.7.15" [patch.crates-io] embassy = { git = "https://github.com/embassy-rs/embassy", rev = "5f43c1d37e9db847c7861fe0bd821db62edba9f6" } embassy-rp = { git = "https://github.com/embassy-rs/embassy", rev = "5f43c1d37e9db847c7861fe0bd821db62edba9f6" } +embassy-net = { git = "https://github.com/embassy-rs/embassy", rev = "5f43c1d37e9db847c7861fe0bd821db62edba9f6" } #embassy = { path = "/home/dirbaio/embassy/embassy/embassy" } #embassy-rp = { path = "/home/dirbaio/embassy/embassy/embassy-rp" } +#embassy-net = { path = "/home/dirbaio/embassy/embassy/embassy-net" } +#smoltcp = { path = "./smoltcp" } + +#[patch."https://github.com/smoltcp-rs/smoltcp"] +#smoltcp = { path = "./smoltcp" } [profile.dev] debug = 2 diff --git a/examples/rpi-pico-w/src/main.rs b/examples/rpi-pico-w/src/main.rs index 6d161414..e08ee8e9 100644 --- a/examples/rpi-pico-w/src/main.rs +++ b/examples/rpi-pico-w/src/main.rs @@ -8,9 +8,13 @@ use defmt::{assert, assert_eq, panic, *}; use embassy::executor::Spawner; use embassy::time::{Duration, Timer}; use embassy::util::Forever; +use embassy_net::tcp::TcpSocket; +use embassy_net::{Ipv4Address, Ipv4Cidr, Stack, StackResources}; use embassy_rp::gpio::{Flex, Level, Output, Pin}; use embassy_rp::peripherals::{PIN_23, PIN_24, PIN_25, PIN_29}; use embassy_rp::Peripherals; +use embedded_io::asynch::{Read, Write}; +use heapless::Vec; use {defmt_rtt as _, panic_probe as _}; macro_rules! forever { @@ -26,6 +30,11 @@ async fn wifi_task(runner: cyw43::Runner<'static, PIN_23, PIN_25, PIN_29, PIN_24 runner.run().await } +#[embassy::task] +async fn net_task(stack: &'static Stack>) -> ! { + stack.run().await +} + #[embassy::main] async fn main(spawner: Spawner, p: Peripherals) { info!("Hello World!"); @@ -45,8 +54,71 @@ async fn main(spawner: Spawner, p: Peripherals) { spawner.spawn(wifi_task(runner)).unwrap(); - control.init().await; + let net_device = control.init().await; - //control.join_open("MikroTik-951589").await; - control.join_wpa2("MikroTik-951589", "fasdfasdfasdf").await; + control.join_open("MikroTik-951589").await; + //control.join_wpa2("MikroTik-951589", "asdfasdfasdfasdf").await; + + let config = embassy_net::ConfigStrategy::Dhcp; + //let config = embassy_net::ConfigStrategy::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 = &*forever!(Stack::new( + net_device, + config, + forever!(StackResources::<1, 2, 8>::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_net::SmolDuration::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/src/lib.rs b/src/lib.rs index e0c2c931..dde9d9c3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -12,12 +12,19 @@ mod structs; use core::cell::Cell; use core::slice; +use core::sync::atomic::Ordering; +use core::task::Waker; +use atomic_polyfill::AtomicBool; +use embassy::blocking_mutex::raw::NoopRawMutex; +use embassy::channel::mpmc::Channel; use embassy::time::{block_for, Duration, Timer}; use embassy::util::yield_now; +use embassy_net::{PacketBoxExt, PacketBuf}; use embassy_rp::gpio::{Flex, Output, Pin}; use self::structs::*; +use crate::events::Event; fn swap16(x: u32) -> u32 { (x & 0xFF00FF00) >> 8 | (x & 0x00FF00FF) << 8 @@ -197,6 +204,10 @@ enum IoctlState { pub struct State { ioctl_id: Cell, ioctl_state: Cell, + + tx_channel: Channel, + rx_channel: Channel, + link_up: AtomicBool, } impl State { @@ -204,6 +215,10 @@ impl State { Self { ioctl_id: Cell::new(0), ioctl_state: Cell::new(IoctlState::Idle), + + tx_channel: Channel::new(), + rx_channel: Channel::new(), + link_up: AtomicBool::new(true), // TODO set up/down as we join/deassociate } } } @@ -213,7 +228,7 @@ pub struct Control<'a> { } impl<'a> Control<'a> { - pub async fn init(&mut self) { + pub async fn init(&mut self) -> NetDevice<'a> { const CHUNK_SIZE: usize = 1024; let clm = unsafe { slice::from_raw_parts(0x10140000 as *const u8, 4752) }; @@ -253,6 +268,15 @@ impl<'a> Control<'a> { self.set_iovar_u32("apsta", 1).await; //self.set_iovar("cur_etheraddr", &[02, 03, 04, 05, 06, 07]).await; + // read MAC addr. + let mut mac_addr = [0; 6]; + assert_eq!(self.get_iovar("cur_etheraddr", &mut mac_addr).await, 6); + info!("mac addr: {:02x}", mac_addr); + + // TODO get_iovar is broken, it returns all zeros. + // Harcdode our own MAC for now. + let mac_addr = [0x28, 0xCD, 0xC1, 0x00, 0x3F, 0x05]; + let country = countries::WORLD_WIDE_XX; let country_info = CountryInfo { country_abbrev: [country.code[0], country.code[1], 0, 0], @@ -283,6 +307,15 @@ impl<'a> Control<'a> { iface: 0, events: [0xFF; 24], }; + + // Disable spammy uninteresting events. + evts.unset(Event::RADIO); + evts.unset(Event::IF); + evts.unset(Event::PROBREQ_MSG); + evts.unset(Event::PROBREQ_MSG_RX); + evts.unset(Event::PROBRESP_MSG); + evts.unset(Event::PROBRESP_MSG); + self.set_iovar("bsscfg:event_msgs", &evts.to_bytes()).await; Timer::after(Duration::from_millis(100)).await; @@ -305,6 +338,11 @@ impl<'a> Control<'a> { Timer::after(Duration::from_millis(100)).await; info!("INIT DONE"); + + NetDevice { + state: self.state, + mac_addr, + } } pub async fn join_open(&mut self, ssid: &str) { @@ -387,6 +425,7 @@ impl<'a> Control<'a> { self.ioctl(2, 263, 0, &mut buf).await; } + // TODO this is not really working, it always returns all zeros. async fn get_iovar(&mut self, name: &str, res: &mut [u8]) -> usize { info!("get {}", name); @@ -395,7 +434,7 @@ impl<'a> Control<'a> { buf[name.len()] = 0; let total_len = name.len() + 1 + res.len(); - let res_len = self.ioctl(2, 262, 0, &mut buf[..total_len]).await - name.len() - 1; + let res_len = self.ioctl(0, 262, 0, &mut buf[..total_len]).await - name.len() - 1; res[..res_len].copy_from_slice(&buf[name.len() + 1..][..res_len]); res_len } @@ -408,9 +447,6 @@ impl<'a> Control<'a> { async fn ioctl(&mut self, kind: u32, cmd: u32, iface: u32, buf: &mut [u8]) -> usize { // TODO cancel ioctl on future drop. - // snail mode 🐌 - Timer::after(Duration::from_millis(100)).await; - while !matches!(self.state.ioctl_state.get(), IoctlState::Idle) { yield_now().await; } @@ -434,6 +470,50 @@ impl<'a> Control<'a> { } } +pub struct NetDevice<'a> { + state: &'a State, + mac_addr: [u8; 6], +} + +impl<'a> embassy_net::Device for NetDevice<'a> { + fn register_waker(&mut self, waker: &Waker) { + // loopy loopy wakey wakey + waker.wake_by_ref() + } + + fn link_state(&mut self) -> embassy_net::LinkState { + match self.state.link_up.load(Ordering::Relaxed) { + true => embassy_net::LinkState::Up, + false => embassy_net::LinkState::Down, + } + } + + fn capabilities(&self) -> embassy_net::DeviceCapabilities { + let mut caps = embassy_net::DeviceCapabilities::default(); + caps.max_transmission_unit = 1514; // 1500 IP + 14 ethernet header + caps.medium = embassy_net::Medium::Ethernet; + caps + } + + fn is_transmit_ready(&mut self) -> bool { + true + } + + fn transmit(&mut self, pkt: PacketBuf) { + if self.state.tx_channel.try_send(pkt).is_err() { + warn!("TX failed") + } + } + + fn receive(&mut self) -> Option { + self.state.rx_channel.try_recv().ok() + } + + fn ethernet_address(&self) -> [u8; 6] { + self.mac_addr + } +} + pub struct Runner<'a, PWR: Pin, CS: Pin, CLK: Pin, DIO: Pin> { state: &'a State, @@ -576,7 +656,10 @@ impl<'a, PWR: Pin, CS: Pin, CLK: Pin, DIO: Pin> Runner<'a, PWR, CS, CLK, DIO> { while self.read32(FUNC_BUS, REG_BUS_STATUS) & STATUS_F2_RX_READY == 0 {} // Some random configs related to sleep. - // I think they're not needed if we don't want sleep...??? + // These aren't needed if we don't want to sleep the bus. + // TODO do we need to sleep the bus to read the irq line, due to + // being on the same pin as MOSI/MISO? + /* let mut val = self.read8(FUNC_BACKPLANE, REG_BACKPLANE_WAKEUP_CTRL); val |= 0x02; // WAKE_TILL_HT_AVAIL @@ -606,11 +689,16 @@ impl<'a, PWR: Pin, CS: Pin, CLK: Pin, DIO: Pin> Runner<'a, PWR, CS, CLK, DIO> { let mut buf = [0; 2048]; loop { // Send stuff + // TODO flow control if let IoctlState::Pending { kind, cmd, iface, buf } = self.state.ioctl_state.get() { self.send_ioctl(kind, cmd, iface, unsafe { &*buf }, self.state.ioctl_id.get()); self.state.ioctl_state.set(IoctlState::Sent { buf }); } + if let Ok(p) = self.state.tx_channel.try_recv() { + self.send_packet(&p); + } + // Receive stuff let irq = self.read16(FUNC_BUS, REG_BUS_INTERRUPT); @@ -646,6 +734,52 @@ impl<'a, PWR: Pin, CS: Pin, CLK: Pin, DIO: Pin> Runner<'a, PWR, CS, CLK, DIO> { } } + fn send_packet(&mut self, packet: &[u8]) { + trace!("tx pkt {:02x}", &packet[..packet.len().min(48)]); + + let mut buf = [0; 2048]; + + let total_len = SdpcmHeader::SIZE + BcdHeader::SIZE + packet.len(); + + let seq = self.ioctl_seq; + self.ioctl_seq = self.ioctl_seq.wrapping_add(1); + + let sdpcm_header = SdpcmHeader { + len: total_len as u16, // TODO does this len need to be rounded up to u32? + len_inv: !total_len as u16, + sequence: seq, + channel_and_flags: 2, // data channel + next_length: 0, + header_length: SdpcmHeader::SIZE as _, + wireless_flow_control: 0, + bus_data_credit: 0, + reserved: [0, 0], + }; + + let bcd_header = BcdHeader { + flags: 0x20, + priority: 0, + flags2: 0, + data_offset: 0, + }; + trace!("tx {:?}", sdpcm_header); + trace!(" {:?}", bcd_header); + + buf[0..SdpcmHeader::SIZE].copy_from_slice(&sdpcm_header.to_bytes()); + buf[SdpcmHeader::SIZE..][..BcdHeader::SIZE].copy_from_slice(&bcd_header.to_bytes()); + buf[SdpcmHeader::SIZE + BcdHeader::SIZE..][..packet.len()].copy_from_slice(packet); + + let total_len = (total_len + 3) & !3; // round up to 4byte + + trace!(" {:02x}", &buf[..total_len.min(48)]); + + let cmd = cmd_word(true, true, FUNC_WLAN, 0, total_len as _); + self.cs.set_low(); + self.spi_write(&cmd.to_le_bytes()); + self.spi_write(&buf[..total_len]); + self.cs.set_high(); + } + fn rx(&mut self, packet: &[u8]) { if packet.len() < SdpcmHeader::SIZE { warn!("packet too short, len={}", packet.len()); @@ -683,6 +817,8 @@ impl<'a, PWR: Pin, CS: Pin, CLK: Pin, DIO: Pin> Runner<'a, PWR, CS, CLK, DIO> { assert_eq!(cdc_header.status, 0); // todo propagate error instead let resp_len = cdc_header.len as usize; + info!("IOCTL Response: {:02x}", &payload[CdcHeader::SIZE..][..resp_len]); + (unsafe { &mut *buf }[..resp_len]).copy_from_slice(&payload[CdcHeader::SIZE..][..resp_len]); self.state.ioctl_state.set(IoctlState::Done { resp_len }); } @@ -703,14 +839,32 @@ impl<'a, PWR: Pin, CS: Pin, CLK: Pin, DIO: Pin> Runner<'a, PWR, CS, CLK, DIO> { let mut evt = EventHeader::from_bytes(&packet[24..][..EventHeader::SIZE].try_into().unwrap()); evt.byteswap(); let evt_data = &packet[24 + EventHeader::SIZE..][..evt.datalen as usize]; - info!( + debug!( "=== EVENT {}: {} {:02x}", events::Event::from(evt.event_type as u8), evt, evt_data ); } + 2 => { + let bcd_header = BcdHeader::from_bytes(&payload[..BcdHeader::SIZE].try_into().unwrap()); + trace!(" {:?}", bcd_header); + let packet_start = BcdHeader::SIZE + 4 * bcd_header.data_offset as usize; + if packet_start > payload.len() { + warn!("packet start out of range."); + return; + } + let packet = &payload[packet_start..]; + trace!("rx pkt {:02x}", &packet[..(packet.len() as usize).min(48)]); + + let mut p = unwrap!(embassy_net::PacketBox::new(embassy_net::Packet::new())); + p[..packet.len()].copy_from_slice(packet); + + if let Err(_) = self.state.rx_channel.try_send(p.slice(0..packet.len())) { + warn!("failed to push rxd packet to the channel.") + } + } _ => {} } } diff --git a/src/structs.rs b/src/structs.rs index dd2c0cfe..060c2b06 100644 --- a/src/structs.rs +++ b/src/structs.rs @@ -1,3 +1,5 @@ +use crate::events::Event; + macro_rules! impl_bytes { ($t:ident) => { impl $t { @@ -157,3 +159,10 @@ pub struct EventMask { pub events: [u8; 24], } impl_bytes!(EventMask); + +impl EventMask { + pub fn unset(&mut self, evt: Event) { + let evt = evt as u8 as usize; + self.events[evt / 8] &= !(1 << (evt % 8)); + } +} From 31410aa5b7d570184b0abcb78d04654d939660db Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Wed, 13 Jul 2022 21:22:52 +0200 Subject: [PATCH 011/178] update rust nightly to match embassy. --- rust-toolchain.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 03da4cf7..0fa7cf7b 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,7 +1,7 @@ # Before upgrading check that everything is available on all tier1 targets here: # https://rust-lang.github.io/rustup-components-history [toolchain] -channel = "nightly-2022-07-09" +channel = "nightly-2022-07-13" components = [ "rust-src", "rustfmt" ] targets = [ "thumbv6m-none-eabi", From 7dfdea87971bf7a951b2b1d3fc2e50e245005d07 Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Fri, 15 Jul 2022 18:33:32 +0200 Subject: [PATCH 012/178] Switch to embedded-hal SPI, GPIO traits. --- .vscode/settings.json | 2 +- Cargo.toml | 3 +- examples/rpi-pico-w/src/main.rs | 125 ++++++-- src/lib.rs | 490 +++++++++++++++++--------------- 4 files changed, 364 insertions(+), 256 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index cc926d04..748816bb 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -8,7 +8,7 @@ "rust-analyzer.imports.granularity.enforce": true, "rust-analyzer.imports.granularity.group": "module", "rust-analyzer.procMacro.attributes.enable": false, - "rust-analyzer.procMacro.enable": true, + "rust-analyzer.procMacro.enable": false, "rust-analyzer.linkedProjects": [ "examples/rpi-pico-w/Cargo.toml", ], diff --git a/Cargo.toml b/Cargo.toml index 31a14f96..d35e865b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,11 +4,10 @@ version = "0.1.0" edition = "2021" [features] -defmt = ["dep:defmt", "embassy-rp/defmt", "embassy/defmt"] +defmt = ["dep:defmt", "embassy/defmt"] log = ["dep:log"] [dependencies] embassy = { version = "0.1.0" } -embassy-rp = { version = "0.1.0", features = ["unstable-traits", "nightly", "unstable-pac"] } embassy-net = { version = "0.1.0" } atomic-polyfill = "0.1.5" diff --git a/examples/rpi-pico-w/src/main.rs b/examples/rpi-pico-w/src/main.rs index e08ee8e9..655535f9 100644 --- a/examples/rpi-pico-w/src/main.rs +++ b/examples/rpi-pico-w/src/main.rs @@ -1,8 +1,9 @@ #![no_std] #![no_main] -#![feature(type_alias_impl_trait, concat_bytes)] +#![feature(generic_associated_types, type_alias_impl_trait)] -use core::slice; +use core::convert::Infallible; +use core::future::Future; use defmt::{assert, assert_eq, panic, *}; use embassy::executor::Spawner; @@ -13,8 +14,9 @@ use embassy_net::{Ipv4Address, Ipv4Cidr, Stack, StackResources}; use embassy_rp::gpio::{Flex, Level, Output, Pin}; use embassy_rp::peripherals::{PIN_23, PIN_24, PIN_25, PIN_29}; use embassy_rp::Peripherals; +use embedded_hal_1::spi::ErrorType; +use embedded_hal_async::spi::{ExclusiveDevice, SpiBusFlush, SpiBusRead, SpiBusWrite}; use embedded_io::asynch::{Read, Write}; -use heapless::Vec; use {defmt_rtt as _, panic_probe as _}; macro_rules! forever { @@ -26,7 +28,9 @@ macro_rules! forever { } #[embassy::task] -async fn wifi_task(runner: cyw43::Runner<'static, PIN_23, PIN_25, PIN_29, PIN_24>) -> ! { +async fn wifi_task( + runner: cyw43::Runner<'static, Output<'static, PIN_23>, ExclusiveDevice>>, +) -> ! { runner.run().await } @@ -39,25 +43,25 @@ async fn net_task(stack: &'static Stack>) -> ! { async fn main(spawner: Spawner, p: Peripherals) { info!("Hello World!"); - let (pwr, cs, clk, dio) = (p.PIN_23, p.PIN_25, p.PIN_29, p.PIN_24); - //let (pwr, cs, clk, dio) = (p.PIN_23, p.PIN_0, p.PIN_1, p.PIN_2); + let pwr = Output::new(p.PIN_23, Level::Low); + let cs = Output::new(p.PIN_25, Level::High); + let clk = Output::new(p.PIN_29, Level::Low); + let mut dio = Flex::new(p.PIN_24); + dio.set_low(); + dio.set_as_output(); + + let bus = MySpi { clk, dio }; + let spi = ExclusiveDevice::new(bus, cs); let state = forever!(cyw43::State::new()); - let (mut control, runner) = cyw43::new( - state, - Output::new(pwr, Level::Low), - Output::new(cs, Level::High), - Output::new(clk, Level::Low), - Flex::new(dio), - ) - .await; + let (mut control, runner) = cyw43::new(state, pwr, spi).await; spawner.spawn(wifi_task(runner)).unwrap(); let net_device = control.init().await; - control.join_open("MikroTik-951589").await; - //control.join_wpa2("MikroTik-951589", "asdfasdfasdfasdf").await; + //control.join_open("MikroTik-951589").await; + control.join_wpa2("DirbaioWifi", "HelloWorld").await; let config = embassy_net::ConfigStrategy::Dhcp; //let config = embassy_net::ConfigStrategy::Static(embassy_net::Config { @@ -122,3 +126,92 @@ async fn main(spawner: Spawner, p: Peripherals) { } } } + +struct MySpi { + /// SPI clock + clk: Output<'static, PIN_29>, + + /// 4 signals, all in one!! + /// - SPI MISO + /// - SPI MOSI + /// - IRQ + /// - strap to set to gSPI mode on boot. + dio: Flex<'static, PIN_24>, +} + +impl ErrorType for MySpi { + type Error = Infallible; +} + +impl SpiBusFlush for MySpi { + type FlushFuture<'a> = impl Future> + where + Self: 'a; + + fn flush<'a>(&'a mut self) -> Self::FlushFuture<'a> { + async move { Ok(()) } + } +} + +impl SpiBusRead for MySpi { + type ReadFuture<'a> = impl Future> + where + Self: 'a; + + fn read<'a>(&'a mut self, words: &'a mut [u8]) -> Self::ReadFuture<'a> { + async move { + self.dio.set_as_input(); + for word in words { + let mut w = 0; + for _ in 0..8 { + w = w << 1; + + // rising edge, sample data + if self.dio.is_high() { + w |= 0x01; + } + self.clk.set_high(); + + // falling edge + self.clk.set_low(); + } + *word = w + } + + Ok(()) + } + } +} + +impl SpiBusWrite for MySpi { + type WriteFuture<'a> = impl Future> + where + Self: 'a; + + fn write<'a>(&'a mut self, words: &'a [u8]) -> Self::WriteFuture<'a> { + async move { + self.dio.set_as_output(); + for word in words { + let mut word = *word; + for _ in 0..8 { + // falling edge, setup data + self.clk.set_low(); + if word & 0x80 == 0 { + self.dio.set_low(); + } else { + self.dio.set_high(); + } + + // rising edge + self.clk.set_high(); + + word = word << 1; + } + } + self.clk.set_low(); + + self.dio.set_as_input(); + Ok(()) + } + } +} diff --git a/src/lib.rs b/src/lib.rs index dde9d9c3..fb693c32 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,6 @@ #![no_std] #![no_main] -#![feature(type_alias_impl_trait, concat_bytes, const_slice_from_raw_parts)] +#![feature(type_alias_impl_trait, concat_bytes)] #![deny(unused_must_use)] // This mod MUST go first, so that the others see its macros. @@ -21,7 +21,8 @@ use embassy::channel::mpmc::Channel; use embassy::time::{block_for, Duration, Timer}; use embassy::util::yield_now; use embassy_net::{PacketBoxExt, PacketBuf}; -use embassy_rp::gpio::{Flex, Output, Pin}; +use embedded_hal_1::digital::blocking::OutputPin; +use embedded_hal_async::spi::{SpiBusRead, SpiBusWrite, SpiDevice}; use self::structs::*; use crate::events::Event; @@ -514,41 +515,26 @@ impl<'a> embassy_net::Device for NetDevice<'a> { } } -pub struct Runner<'a, PWR: Pin, CS: Pin, CLK: Pin, DIO: Pin> { +pub struct Runner<'a, PWR, SPI> { state: &'a State, - pwr: Output<'a, PWR>, - - /// SPI chip-select. - cs: Output<'a, CS>, - - /// SPI clock - clk: Output<'a, CLK>, - - /// 4 signals, all in one!! - /// - SPI MISO - /// - SPI MOSI - /// - IRQ - /// - strap to set to gSPI mode on boot. - dio: Flex<'a, DIO>, + pwr: PWR, + spi: SPI, ioctl_seq: u8, backplane_window: u32, } -pub async fn new<'a, PWR: Pin, CS: Pin, CLK: Pin, DIO: Pin>( - state: &'a State, - pwr: Output<'a, PWR>, - cs: Output<'a, CS>, - clk: Output<'a, CLK>, - dio: Flex<'a, DIO>, -) -> (Control<'a>, Runner<'a, PWR, CS, CLK, DIO>) { +pub async fn new<'a, PWR, SPI>(state: &'a State, pwr: PWR, spi: SPI) -> (Control<'a>, Runner<'a, PWR, SPI>) +where + PWR: OutputPin, + SPI: SpiDevice, + SPI::Bus: SpiBusRead + SpiBusWrite, +{ let mut runner = Runner { state, pwr, - cs, - clk, - dio, + spi, ioctl_seq: 0, backplane_window: 0xAAAA_AAAA, @@ -559,52 +545,53 @@ pub async fn new<'a, PWR: Pin, CS: Pin, CLK: Pin, DIO: Pin>( (Control { state }, runner) } -impl<'a, PWR: Pin, CS: Pin, CLK: Pin, DIO: Pin> Runner<'a, PWR, CS, CLK, DIO> { +impl<'a, PWR, SPI> Runner<'a, PWR, SPI> +where + PWR: OutputPin, + SPI: SpiDevice, + SPI::Bus: SpiBusRead + SpiBusWrite, +{ async fn init(&mut self) { - // Set strap to select gSPI mode. - self.dio.set_as_output(); - self.dio.set_low(); - // Reset - self.pwr.set_low(); + self.pwr.set_low().unwrap(); Timer::after(Duration::from_millis(20)).await; - self.pwr.set_high(); + self.pwr.set_high().unwrap(); Timer::after(Duration::from_millis(250)).await; info!("waiting for ping..."); - while self.read32_swapped(REG_BUS_FEEDBEAD) != FEEDBEAD {} + while self.read32_swapped(REG_BUS_FEEDBEAD).await != FEEDBEAD {} info!("ping ok"); - self.write32_swapped(0x18, TEST_PATTERN); - let val = self.read32_swapped(REG_BUS_TEST); + self.write32_swapped(0x18, TEST_PATTERN).await; + let val = self.read32_swapped(REG_BUS_TEST).await; assert_eq!(val, TEST_PATTERN); // 32bit, big endian. - self.write32_swapped(REG_BUS_CTRL, 0x00010033); + self.write32_swapped(REG_BUS_CTRL, 0x00010033).await; - let val = self.read32(FUNC_BUS, REG_BUS_FEEDBEAD); + let val = self.read32(FUNC_BUS, REG_BUS_FEEDBEAD).await; assert_eq!(val, FEEDBEAD); - let val = self.read32(FUNC_BUS, REG_BUS_TEST); + let val = self.read32(FUNC_BUS, REG_BUS_TEST).await; assert_eq!(val, TEST_PATTERN); // No response delay in any of the funcs. // seems to break backplane??? eat the 4-byte delay instead, that's what the vendor drivers do... - //self.write32(FUNC_BUS, REG_BUS_RESP_DELAY, 0); + //self.write32(FUNC_BUS, REG_BUS_RESP_DELAY, 0).await; // Init ALP (no idea what that stands for) clock - self.write8(FUNC_BACKPLANE, REG_BACKPLANE_CHIP_CLOCK_CSR, 0x08); + self.write8(FUNC_BACKPLANE, REG_BACKPLANE_CHIP_CLOCK_CSR, 0x08).await; info!("waiting for clock..."); - while self.read8(FUNC_BACKPLANE, REG_BACKPLANE_CHIP_CLOCK_CSR) & 0x40 == 0 {} + while self.read8(FUNC_BACKPLANE, REG_BACKPLANE_CHIP_CLOCK_CSR).await & 0x40 == 0 {} info!("clock ok"); - let chip_id = self.bp_read16(0x1800_0000); + let chip_id = self.bp_read16(0x1800_0000).await; info!("chip ID: {}", chip_id); // Upload firmware. - self.core_disable(Core::WLAN); - self.core_reset(Core::SOCSRAM); - self.bp_write32(CHIP.socsram_base_address + 0x10, 3); - self.bp_write32(CHIP.socsram_base_address + 0x44, 0); + self.core_disable(Core::WLAN).await; + self.core_reset(Core::SOCSRAM).await; + self.bp_write32(CHIP.socsram_base_address + 0x10, 3).await; + self.bp_write32(CHIP.socsram_base_address + 0x44, 0).await; let ram_addr = CHIP.atcm_ram_base_address; @@ -618,42 +605,44 @@ impl<'a, PWR: Pin, CS: Pin, CLK: Pin, DIO: Pin> Runner<'a, PWR, CS, CLK, DIO> { let fw = unsafe { slice::from_raw_parts(0x10100000 as *const u8, 224190) }; info!("loading fw"); - self.bp_write(ram_addr, fw); + self.bp_write(ram_addr, fw).await; info!("verifying fw"); let mut buf = [0; 1024]; for (i, chunk) in fw.chunks(1024).enumerate() { let buf = &mut buf[..chunk.len()]; - self.bp_read(ram_addr + i as u32 * 1024, buf); + self.bp_read(ram_addr + i as u32 * 1024, buf).await; assert_eq!(chunk, buf); } info!("loading nvram"); // Round up to 4 bytes. let nvram_len = (NVRAM.len() + 3) / 4 * 4; - self.bp_write(ram_addr + CHIP.chip_ram_size - 4 - nvram_len as u32, NVRAM); + self.bp_write(ram_addr + CHIP.chip_ram_size - 4 - nvram_len as u32, NVRAM) + .await; let nvram_len_words = nvram_len as u32 / 4; let nvram_len_magic = (!nvram_len_words << 16) | nvram_len_words; - self.bp_write32(ram_addr + CHIP.chip_ram_size - 4, nvram_len_magic); + self.bp_write32(ram_addr + CHIP.chip_ram_size - 4, nvram_len_magic) + .await; // Start core! info!("starting up core..."); - self.core_reset(Core::WLAN); - assert!(self.core_is_up(Core::WLAN)); + self.core_reset(Core::WLAN).await; + assert!(self.core_is_up(Core::WLAN).await); - while self.read8(FUNC_BACKPLANE, REG_BACKPLANE_CHIP_CLOCK_CSR) & 0x80 == 0 {} + while self.read8(FUNC_BACKPLANE, REG_BACKPLANE_CHIP_CLOCK_CSR).await & 0x80 == 0 {} // "Set up the interrupt mask and enable interrupts" - self.bp_write32(CHIP.sdiod_core_base_address + 0x24, 0xF0); + self.bp_write32(CHIP.sdiod_core_base_address + 0x24, 0xF0).await; // "Lower F2 Watermark to avoid DMA Hang in F2 when SD Clock is stopped." // Sounds scary... - self.write8(FUNC_BACKPLANE, REG_BACKPLANE_FUNCTION2_WATERMARK, 32); + self.write8(FUNC_BACKPLANE, REG_BACKPLANE_FUNCTION2_WATERMARK, 32).await; // wait for wifi startup info!("waiting for wifi init..."); - while self.read32(FUNC_BUS, REG_BUS_STATUS) & STATUS_F2_RX_READY == 0 {} + while self.read32(FUNC_BUS, REG_BUS_STATUS).await & STATUS_F2_RX_READY == 0 {} // Some random configs related to sleep. // These aren't needed if we don't want to sleep the bus. @@ -661,25 +650,25 @@ impl<'a, PWR: Pin, CS: Pin, CLK: Pin, DIO: Pin> Runner<'a, PWR, CS, CLK, DIO> { // being on the same pin as MOSI/MISO? /* - let mut val = self.read8(FUNC_BACKPLANE, REG_BACKPLANE_WAKEUP_CTRL); + let mut val = self.read8(FUNC_BACKPLANE, REG_BACKPLANE_WAKEUP_CTRL).await; val |= 0x02; // WAKE_TILL_HT_AVAIL - self.write8(FUNC_BACKPLANE, REG_BACKPLANE_WAKEUP_CTRL, val); - self.write8(FUNC_BUS, 0xF0, 0x08); // SDIOD_CCCR_BRCM_CARDCAP.CMD_NODEC = 1 - self.write8(FUNC_BACKPLANE, REG_BACKPLANE_CHIP_CLOCK_CSR, 0x02); // SBSDIO_FORCE_HT + self.write8(FUNC_BACKPLANE, REG_BACKPLANE_WAKEUP_CTRL, val).await; + self.write8(FUNC_BUS, 0xF0, 0x08).await; // SDIOD_CCCR_BRCM_CARDCAP.CMD_NODEC = 1 + self.write8(FUNC_BACKPLANE, REG_BACKPLANE_CHIP_CLOCK_CSR, 0x02).await; // SBSDIO_FORCE_HT - let mut val = self.read8(FUNC_BACKPLANE, REG_BACKPLANE_SLEEP_CSR); + let mut val = self.read8(FUNC_BACKPLANE, REG_BACKPLANE_SLEEP_CSR).await; val |= 0x01; // SBSDIO_SLPCSR_KEEP_SDIO_ON - self.write8(FUNC_BACKPLANE, REG_BACKPLANE_SLEEP_CSR, val); + self.write8(FUNC_BACKPLANE, REG_BACKPLANE_SLEEP_CSR, val).await; */ // clear pulls - self.write8(FUNC_BACKPLANE, REG_BACKPLANE_PULL_UP, 0); - let _ = self.read8(FUNC_BACKPLANE, REG_BACKPLANE_PULL_UP); + self.write8(FUNC_BACKPLANE, REG_BACKPLANE_PULL_UP, 0).await; + let _ = self.read8(FUNC_BACKPLANE, REG_BACKPLANE_PULL_UP).await; // start HT clock - //self.write8(FUNC_BACKPLANE, REG_BACKPLANE_CHIP_CLOCK_CSR, 0x10); + //self.write8(FUNC_BACKPLANE, REG_BACKPLANE_CHIP_CLOCK_CSR, 0x10).await; //info!("waiting for HT clock..."); - //while self.read8(FUNC_BACKPLANE, REG_BACKPLANE_CHIP_CLOCK_CSR) & 0x80 == 0 {} + //while self.read8(FUNC_BACKPLANE, REG_BACKPLANE_CHIP_CLOCK_CSR).await & 0x80 == 0 {} //info!("clock ok"); info!("init done "); @@ -691,21 +680,22 @@ impl<'a, PWR: Pin, CS: Pin, CLK: Pin, DIO: Pin> Runner<'a, PWR, CS, CLK, DIO> { // Send stuff // TODO flow control if let IoctlState::Pending { kind, cmd, iface, buf } = self.state.ioctl_state.get() { - self.send_ioctl(kind, cmd, iface, unsafe { &*buf }, self.state.ioctl_id.get()); + self.send_ioctl(kind, cmd, iface, unsafe { &*buf }, self.state.ioctl_id.get()) + .await; self.state.ioctl_state.set(IoctlState::Sent { buf }); } if let Ok(p) = self.state.tx_channel.try_recv() { - self.send_packet(&p); + self.send_packet(&p).await; } // Receive stuff - let irq = self.read16(FUNC_BUS, REG_BUS_INTERRUPT); + let irq = self.read16(FUNC_BUS, REG_BUS_INTERRUPT).await; if irq & IRQ_F2_PACKET_AVAILABLE != 0 { let mut status = 0xFFFF_FFFF; while status == 0xFFFF_FFFF { - status = self.read32(FUNC_BUS, REG_BUS_STATUS); + status = self.read32(FUNC_BUS, REG_BUS_STATUS).await; } if status & STATUS_F2_PKT_AVAILABLE != 0 { @@ -713,15 +703,23 @@ impl<'a, PWR: Pin, CS: Pin, CLK: Pin, DIO: Pin> Runner<'a, PWR, CS, CLK, DIO> { let cmd = cmd_word(false, true, FUNC_WLAN, 0, len); - self.cs.set_low(); - self.spi_write(&cmd.to_le_bytes()); - self.spi_read(&mut buf[..len as usize]); - // pad to 32bit - let mut junk = [0; 4]; - if len % 4 != 0 { - self.spi_read(&mut junk[..(4 - len as usize % 4)]); - } - self.cs.set_high(); + self.spi + .transaction(|bus| { + let bus = unsafe { &mut *bus }; + async { + bus.write(&cmd.to_le_bytes()).await?; + bus.read(&mut buf[..len as usize]).await?; + // pad to 32bit + let mut junk = [0; 4]; + if len % 4 != 0 { + bus.read(&mut junk[..(4 - len as usize % 4)]).await?; + } + + Ok(()) + } + }) + .await + .unwrap(); trace!("rx {:02x}", &buf[..(len as usize).min(48)]); @@ -734,7 +732,7 @@ impl<'a, PWR: Pin, CS: Pin, CLK: Pin, DIO: Pin> Runner<'a, PWR, CS, CLK, DIO> { } } - fn send_packet(&mut self, packet: &[u8]) { + async fn send_packet(&mut self, packet: &[u8]) { trace!("tx pkt {:02x}", &packet[..packet.len().min(48)]); let mut buf = [0; 2048]; @@ -774,10 +772,17 @@ impl<'a, PWR: Pin, CS: Pin, CLK: Pin, DIO: Pin> Runner<'a, PWR, CS, CLK, DIO> { trace!(" {:02x}", &buf[..total_len.min(48)]); let cmd = cmd_word(true, true, FUNC_WLAN, 0, total_len as _); - self.cs.set_low(); - self.spi_write(&cmd.to_le_bytes()); - self.spi_write(&buf[..total_len]); - self.cs.set_high(); + self.spi + .transaction(|bus| { + let bus = unsafe { &mut *bus }; + async { + bus.write(&cmd.to_le_bytes()).await?; + bus.write(&buf[..total_len]).await?; + Ok(()) + } + }) + .await + .unwrap(); } fn rx(&mut self, packet: &[u8]) { @@ -869,7 +874,7 @@ impl<'a, PWR: Pin, CS: Pin, CLK: Pin, DIO: Pin> Runner<'a, PWR, CS, CLK, DIO> { } } - fn send_ioctl(&mut self, kind: u32, cmd: u32, iface: u32, data: &[u8], id: u16) { + async fn send_ioctl(&mut self, kind: u32, cmd: u32, iface: u32, data: &[u8], id: u16) { let mut buf = [0; 2048]; let total_len = SdpcmHeader::SIZE + CdcHeader::SIZE + data.len(); @@ -908,60 +913,69 @@ impl<'a, PWR: Pin, CS: Pin, CLK: Pin, DIO: Pin> Runner<'a, PWR, CS, CLK, DIO> { trace!(" {:02x}", &buf[..total_len.min(48)]); let cmd = cmd_word(true, true, FUNC_WLAN, 0, total_len as _); - self.cs.set_low(); - self.spi_write(&cmd.to_le_bytes()); - self.spi_write(&buf[..total_len]); - self.cs.set_high(); + + self.spi + .transaction(|bus| { + let bus = unsafe { &mut *bus }; + async { + bus.write(&cmd.to_le_bytes()).await?; + bus.write(&buf[..total_len]).await?; + Ok(()) + } + }) + .await + .unwrap(); } - fn core_disable(&mut self, core: Core) { + async fn core_disable(&mut self, core: Core) { let base = core.base_addr(); // Dummy read? - let _ = self.bp_read8(base + AI_RESETCTRL_OFFSET); + let _ = self.bp_read8(base + AI_RESETCTRL_OFFSET).await; // Check it isn't already reset - let r = self.bp_read8(base + AI_RESETCTRL_OFFSET); + let r = self.bp_read8(base + AI_RESETCTRL_OFFSET).await; if r & AI_RESETCTRL_BIT_RESET != 0 { return; } - self.bp_write8(base + AI_IOCTRL_OFFSET, 0); - let _ = self.bp_read8(base + AI_IOCTRL_OFFSET); + self.bp_write8(base + AI_IOCTRL_OFFSET, 0).await; + let _ = self.bp_read8(base + AI_IOCTRL_OFFSET).await; block_for(Duration::from_millis(1)); - self.bp_write8(base + AI_RESETCTRL_OFFSET, AI_RESETCTRL_BIT_RESET); - let _ = self.bp_read8(base + AI_RESETCTRL_OFFSET); + self.bp_write8(base + AI_RESETCTRL_OFFSET, AI_RESETCTRL_BIT_RESET).await; + let _ = self.bp_read8(base + AI_RESETCTRL_OFFSET).await; } - fn core_reset(&mut self, core: Core) { - self.core_disable(core); + async fn core_reset(&mut self, core: Core) { + self.core_disable(core).await; let base = core.base_addr(); - self.bp_write8(base + AI_IOCTRL_OFFSET, AI_IOCTRL_BIT_FGC | AI_IOCTRL_BIT_CLOCK_EN); - let _ = self.bp_read8(base + AI_IOCTRL_OFFSET); + self.bp_write8(base + AI_IOCTRL_OFFSET, AI_IOCTRL_BIT_FGC | AI_IOCTRL_BIT_CLOCK_EN) + .await; + let _ = self.bp_read8(base + AI_IOCTRL_OFFSET).await; - self.bp_write8(base + AI_RESETCTRL_OFFSET, 0); + self.bp_write8(base + AI_RESETCTRL_OFFSET, 0).await; - block_for(Duration::from_millis(1)); + Timer::after(Duration::from_millis(1)).await; - self.bp_write8(base + AI_IOCTRL_OFFSET, AI_IOCTRL_BIT_CLOCK_EN); - let _ = self.bp_read8(base + AI_IOCTRL_OFFSET); + self.bp_write8(base + AI_IOCTRL_OFFSET, AI_IOCTRL_BIT_CLOCK_EN).await; + let _ = self.bp_read8(base + AI_IOCTRL_OFFSET).await; - block_for(Duration::from_millis(1)); + Timer::after(Duration::from_millis(1)).await; } - fn core_is_up(&mut self, core: Core) -> bool { + async fn core_is_up(&mut self, core: Core) -> bool { let base = core.base_addr(); - let io = self.bp_read8(base + AI_IOCTRL_OFFSET); + let io = self.bp_read8(base + AI_IOCTRL_OFFSET).await; if io & (AI_IOCTRL_BIT_FGC | AI_IOCTRL_BIT_CLOCK_EN) != AI_IOCTRL_BIT_CLOCK_EN { debug!("core_is_up: returning false due to bad ioctrl {:02x}", io); return false; } - let r = self.bp_read8(base + AI_RESETCTRL_OFFSET); + let r = self.bp_read8(base + AI_RESETCTRL_OFFSET).await; if r & (AI_RESETCTRL_BIT_RESET) != 0 { debug!("core_is_up: returning false due to bad resetctrl {:02x}", r); return false; @@ -970,7 +984,7 @@ impl<'a, PWR: Pin, CS: Pin, CLK: Pin, DIO: Pin> Runner<'a, PWR, CS, CLK, DIO> { true } - fn bp_read(&mut self, mut addr: u32, mut data: &mut [u8]) { + async fn bp_read(&mut self, mut addr: u32, mut data: &mut [u8]) { // It seems the HW force-aligns the addr // to 2 if data.len() >= 2 // to 4 if data.len() >= 4 @@ -984,24 +998,32 @@ impl<'a, PWR: Pin, CS: Pin, CLK: Pin, DIO: Pin> Runner<'a, PWR, CS, CLK, DIO> { let len = data.len().min(BACKPLANE_MAX_TRANSFER_SIZE).min(window_remaining); - self.backplane_set_window(addr); + self.backplane_set_window(addr).await; let cmd = cmd_word(false, true, FUNC_BACKPLANE, window_offs, len as u32); - self.cs.set_low(); - self.spi_write(&cmd.to_le_bytes()); - // 4-byte response delay. - let mut junk = [0; 4]; - self.spi_read(&mut junk); + self.spi + .transaction(|bus| { + let bus = unsafe { &mut *bus }; + async { + bus.write(&cmd.to_le_bytes()).await?; - // Read data - self.spi_read(&mut data[..len]); + // 4-byte response delay. + let mut junk = [0; 4]; + bus.read(&mut junk).await?; - // pad to 32bit - if len % 4 != 0 { - self.spi_read(&mut junk[..(4 - len % 4)]); - } - self.cs.set_high(); + // Read data + bus.read(&mut data[..len]).await?; + + // pad to 32bit + if len % 4 != 0 { + bus.read(&mut junk[..(4 - len % 4)]).await?; + } + Ok(()) + } + }) + .await + .unwrap(); // Advance ptr. addr += len as u32; @@ -1009,7 +1031,7 @@ impl<'a, PWR: Pin, CS: Pin, CLK: Pin, DIO: Pin> Runner<'a, PWR, CS, CLK, DIO> { } } - fn bp_write(&mut self, mut addr: u32, mut data: &[u8]) { + async fn bp_write(&mut self, mut addr: u32, mut data: &[u8]) { // It seems the HW force-aligns the addr // to 2 if data.len() >= 2 // to 4 if data.len() >= 4 @@ -1023,18 +1045,26 @@ impl<'a, PWR: Pin, CS: Pin, CLK: Pin, DIO: Pin> Runner<'a, PWR, CS, CLK, DIO> { let len = data.len().min(BACKPLANE_MAX_TRANSFER_SIZE).min(window_remaining); - self.backplane_set_window(addr); + self.backplane_set_window(addr).await; let cmd = cmd_word(true, true, FUNC_BACKPLANE, window_offs, len as u32); - self.cs.set_low(); - self.spi_write(&cmd.to_le_bytes()); - self.spi_write(&data[..len]); - // pad to 32bit - if len % 4 != 0 { - let zeros = [0; 4]; - self.spi_write(&zeros[..(4 - len % 4)]); - } - self.cs.set_high(); + + self.spi + .transaction(|bus| { + let bus = unsafe { &mut *bus }; + async { + bus.write(&cmd.to_le_bytes()).await?; + bus.write(&data[..len]).await?; + // pad to 32bit + if len % 4 != 0 { + let zeros = [0; 4]; + bus.write(&zeros[..(4 - len % 4)]).await?; + } + Ok(()) + } + }) + .await + .unwrap(); // Advance ptr. addr += len as u32; @@ -1042,51 +1072,51 @@ impl<'a, PWR: Pin, CS: Pin, CLK: Pin, DIO: Pin> Runner<'a, PWR, CS, CLK, DIO> { } } - fn bp_read8(&mut self, addr: u32) -> u8 { - self.backplane_readn(addr, 1) as u8 + async fn bp_read8(&mut self, addr: u32) -> u8 { + self.backplane_readn(addr, 1).await as u8 } - fn bp_write8(&mut self, addr: u32, val: u8) { - self.backplane_writen(addr, val as u32, 1) + async fn bp_write8(&mut self, addr: u32, val: u8) { + self.backplane_writen(addr, val as u32, 1).await } - fn bp_read16(&mut self, addr: u32) -> u16 { - self.backplane_readn(addr, 2) as u16 + async fn bp_read16(&mut self, addr: u32) -> u16 { + self.backplane_readn(addr, 2).await as u16 } - fn bp_write16(&mut self, addr: u32, val: u16) { - self.backplane_writen(addr, val as u32, 2) + async fn bp_write16(&mut self, addr: u32, val: u16) { + self.backplane_writen(addr, val as u32, 2).await } - fn bp_read32(&mut self, addr: u32) -> u32 { - self.backplane_readn(addr, 4) + async fn bp_read32(&mut self, addr: u32) -> u32 { + self.backplane_readn(addr, 4).await } - fn bp_write32(&mut self, addr: u32, val: u32) { - self.backplane_writen(addr, val, 4) + async fn bp_write32(&mut self, addr: u32, val: u32) { + self.backplane_writen(addr, val, 4).await } - fn backplane_readn(&mut self, addr: u32, len: u32) -> u32 { - self.backplane_set_window(addr); + async fn backplane_readn(&mut self, addr: u32, len: u32) -> u32 { + self.backplane_set_window(addr).await; let mut bus_addr = addr & BACKPLANE_ADDRESS_MASK; if len == 4 { bus_addr |= BACKPLANE_ADDRESS_32BIT_FLAG } - self.readn(FUNC_BACKPLANE, bus_addr, len) + self.readn(FUNC_BACKPLANE, bus_addr, len).await } - fn backplane_writen(&mut self, addr: u32, val: u32, len: u32) { - self.backplane_set_window(addr); + async fn backplane_writen(&mut self, addr: u32, val: u32, len: u32) { + self.backplane_set_window(addr).await; let mut bus_addr = addr & BACKPLANE_ADDRESS_MASK; if len == 4 { bus_addr |= BACKPLANE_ADDRESS_32BIT_FLAG } - self.writen(FUNC_BACKPLANE, bus_addr, val, len) + self.writen(FUNC_BACKPLANE, bus_addr, val, len).await } - fn backplane_set_window(&mut self, addr: u32) { + async fn backplane_set_window(&mut self, addr: u32) { let new_window = addr & !BACKPLANE_ADDRESS_MASK; if (new_window >> 24) as u8 != (self.backplane_window >> 24) as u8 { @@ -1094,138 +1124,124 @@ impl<'a, PWR: Pin, CS: Pin, CLK: Pin, DIO: Pin> Runner<'a, PWR, CS, CLK, DIO> { FUNC_BACKPLANE, REG_BACKPLANE_BACKPLANE_ADDRESS_HIGH, (new_window >> 24) as u8, - ); + ) + .await; } if (new_window >> 16) as u8 != (self.backplane_window >> 16) as u8 { self.write8( FUNC_BACKPLANE, REG_BACKPLANE_BACKPLANE_ADDRESS_MID, (new_window >> 16) as u8, - ); + ) + .await; } if (new_window >> 8) as u8 != (self.backplane_window >> 8) as u8 { self.write8( FUNC_BACKPLANE, REG_BACKPLANE_BACKPLANE_ADDRESS_LOW, (new_window >> 8) as u8, - ); + ) + .await; } self.backplane_window = new_window; } - fn read8(&mut self, func: u32, addr: u32) -> u8 { - self.readn(func, addr, 1) as u8 + async fn read8(&mut self, func: u32, addr: u32) -> u8 { + self.readn(func, addr, 1).await as u8 } - fn write8(&mut self, func: u32, addr: u32, val: u8) { - self.writen(func, addr, val as u32, 1) + async fn write8(&mut self, func: u32, addr: u32, val: u8) { + self.writen(func, addr, val as u32, 1).await } - fn read16(&mut self, func: u32, addr: u32) -> u16 { - self.readn(func, addr, 2) as u16 + async fn read16(&mut self, func: u32, addr: u32) -> u16 { + self.readn(func, addr, 2).await as u16 } - fn write16(&mut self, func: u32, addr: u32, val: u16) { - self.writen(func, addr, val as u32, 2) + async fn write16(&mut self, func: u32, addr: u32, val: u16) { + self.writen(func, addr, val as u32, 2).await } - fn read32(&mut self, func: u32, addr: u32) -> u32 { - self.readn(func, addr, 4) + async fn read32(&mut self, func: u32, addr: u32) -> u32 { + self.readn(func, addr, 4).await } - fn write32(&mut self, func: u32, addr: u32, val: u32) { - self.writen(func, addr, val, 4) + async fn write32(&mut self, func: u32, addr: u32, val: u32) { + self.writen(func, addr, val, 4).await } - fn readn(&mut self, func: u32, addr: u32, len: u32) -> u32 { + async fn readn(&mut self, func: u32, addr: u32, len: u32) -> u32 { let cmd = cmd_word(false, true, func, addr, len); let mut buf = [0; 4]; - self.cs.set_low(); - self.spi_write(&cmd.to_le_bytes()); - if func == FUNC_BACKPLANE { - // 4-byte response delay. - self.spi_read(&mut buf); - } - self.spi_read(&mut buf); - self.cs.set_high(); + self.spi + .transaction(|bus| { + let bus = unsafe { &mut *bus }; + async { + bus.write(&cmd.to_le_bytes()).await?; + if func == FUNC_BACKPLANE { + // 4-byte response delay. + bus.read(&mut buf).await?; + } + bus.read(&mut buf).await?; + Ok(()) + } + }) + .await + .unwrap(); u32::from_le_bytes(buf) } - fn writen(&mut self, func: u32, addr: u32, val: u32, len: u32) { + async fn writen(&mut self, func: u32, addr: u32, val: u32, len: u32) { let cmd = cmd_word(true, true, func, addr, len); - self.cs.set_low(); - self.spi_write(&cmd.to_le_bytes()); - self.spi_write(&val.to_le_bytes()); - self.cs.set_high(); + self.spi + .transaction(|bus| { + let bus = unsafe { &mut *bus }; + async { + bus.write(&cmd.to_le_bytes()).await?; + bus.write(&val.to_le_bytes()).await?; + Ok(()) + } + }) + .await + .unwrap(); } - fn read32_swapped(&mut self, addr: u32) -> u32 { + async fn read32_swapped(&mut self, addr: u32) -> u32 { let cmd = cmd_word(false, true, FUNC_BUS, addr, 4); let mut buf = [0; 4]; - self.cs.set_low(); - self.spi_write(&swap16(cmd).to_le_bytes()); - self.spi_read(&mut buf); - self.cs.set_high(); + self.spi + .transaction(|bus| { + let bus = unsafe { &mut *bus }; + async { + bus.write(&swap16(cmd).to_le_bytes()).await?; + bus.read(&mut buf).await?; + Ok(()) + } + }) + .await + .unwrap(); swap16(u32::from_le_bytes(buf)) } - fn write32_swapped(&mut self, addr: u32, val: u32) { + async fn write32_swapped(&mut self, addr: u32, val: u32) { let cmd = cmd_word(true, true, FUNC_BUS, addr, 4); - self.cs.set_low(); - self.spi_write(&swap16(cmd).to_le_bytes()); - self.spi_write(&swap16(val).to_le_bytes()); - self.cs.set_high(); - } - - fn spi_read(&mut self, words: &mut [u8]) { - self.dio.set_as_input(); - for word in words { - let mut w = 0; - for _ in 0..8 { - w = w << 1; - - // rising edge, sample data - if self.dio.is_high() { - w |= 0x01; + self.spi + .transaction(|bus| { + let bus = unsafe { &mut *bus }; + async { + bus.write(&swap16(cmd).to_le_bytes()).await?; + bus.write(&swap16(val).to_le_bytes()).await?; + Ok(()) } - self.clk.set_high(); - - // falling edge - self.clk.set_low(); - } - *word = w - } - self.clk.set_low(); - } - - fn spi_write(&mut self, words: &[u8]) { - self.dio.set_as_output(); - for word in words { - let mut word = *word; - for _ in 0..8 { - // falling edge, setup data - self.clk.set_low(); - if word & 0x80 == 0 { - self.dio.set_low(); - } else { - self.dio.set_high(); - } - - // rising edge - self.clk.set_high(); - - word = word << 1; - } - } - self.clk.set_low(); - - self.dio.set_as_input(); + }) + .await + .unwrap(); } } From 931e3d7ee0cb1ff9f320b22aab3f4ea375a1c624 Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Sat, 16 Jul 2022 18:06:57 +0200 Subject: [PATCH 013/178] Switch to 32bit SPI. --- examples/rpi-pico-w/src/main.rs | 14 ++-- src/lib.rs | 112 ++++++++++++++------------------ 2 files changed, 55 insertions(+), 71 deletions(-) diff --git a/examples/rpi-pico-w/src/main.rs b/examples/rpi-pico-w/src/main.rs index 655535f9..475c6906 100644 --- a/examples/rpi-pico-w/src/main.rs +++ b/examples/rpi-pico-w/src/main.rs @@ -153,17 +153,17 @@ impl SpiBusFlush for MySpi { } } -impl SpiBusRead for MySpi { +impl SpiBusRead for MySpi { type ReadFuture<'a> = impl Future> where Self: 'a; - fn read<'a>(&'a mut self, words: &'a mut [u8]) -> Self::ReadFuture<'a> { + fn read<'a>(&'a mut self, words: &'a mut [u32]) -> Self::ReadFuture<'a> { async move { self.dio.set_as_input(); for word in words { let mut w = 0; - for _ in 0..8 { + for _ in 0..32 { w = w << 1; // rising edge, sample data @@ -183,20 +183,20 @@ impl SpiBusRead for MySpi { } } -impl SpiBusWrite for MySpi { +impl SpiBusWrite for MySpi { type WriteFuture<'a> = impl Future> where Self: 'a; - fn write<'a>(&'a mut self, words: &'a [u8]) -> Self::WriteFuture<'a> { + fn write<'a>(&'a mut self, words: &'a [u32]) -> Self::WriteFuture<'a> { async move { self.dio.set_as_output(); for word in words { let mut word = *word; - for _ in 0..8 { + for _ in 0..32 { // falling edge, setup data self.clk.set_low(); - if word & 0x80 == 0 { + if word & 0x8000_0000 == 0 { self.dio.set_low(); } else { self.dio.set_high(); diff --git a/src/lib.rs b/src/lib.rs index fb693c32..8c235b17 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -28,13 +28,18 @@ use self::structs::*; use crate::events::Event; fn swap16(x: u32) -> u32 { - (x & 0xFF00FF00) >> 8 | (x & 0x00FF00FF) << 8 + x.rotate_left(16) } fn cmd_word(write: bool, incr: bool, func: u32, addr: u32, len: u32) -> u32 { (write as u32) << 31 | (incr as u32) << 30 | (func & 0b11) << 28 | (addr & 0x1FFFF) << 11 | (len & 0x7FF) } +fn slice8_mut(x: &mut [u32]) -> &mut [u8] { + let len = x.len() * 4; + unsafe { slice::from_raw_parts_mut(x.as_mut_ptr() as _, len) } +} + const FUNC_BUS: u32 = 0; const FUNC_BACKPLANE: u32 = 1; const FUNC_WLAN: u32 = 2; @@ -529,7 +534,7 @@ pub async fn new<'a, PWR, SPI>(state: &'a State, pwr: PWR, spi: SPI) -> (Control where PWR: OutputPin, SPI: SpiDevice, - SPI::Bus: SpiBusRead + SpiBusWrite, + SPI::Bus: SpiBusRead + SpiBusWrite, { let mut runner = Runner { state, @@ -549,7 +554,7 @@ impl<'a, PWR, SPI> Runner<'a, PWR, SPI> where PWR: OutputPin, SPI: SpiDevice, - SPI::Bus: SpiBusRead + SpiBusWrite, + SPI::Bus: SpiBusRead + SpiBusWrite, { async fn init(&mut self) { // Reset @@ -566,8 +571,8 @@ where let val = self.read32_swapped(REG_BUS_TEST).await; assert_eq!(val, TEST_PATTERN); - // 32bit, big endian. - self.write32_swapped(REG_BUS_CTRL, 0x00010033).await; + // 32bit, little endian. + self.write32_swapped(REG_BUS_CTRL, 0x00010031).await; let val = self.read32(FUNC_BUS, REG_BUS_FEEDBEAD).await; assert_eq!(val, FEEDBEAD); @@ -607,14 +612,6 @@ where info!("loading fw"); self.bp_write(ram_addr, fw).await; - info!("verifying fw"); - let mut buf = [0; 1024]; - for (i, chunk) in fw.chunks(1024).enumerate() { - let buf = &mut buf[..chunk.len()]; - self.bp_read(ram_addr + i as u32 * 1024, buf).await; - assert_eq!(chunk, buf); - } - info!("loading nvram"); // Round up to 4 bytes. let nvram_len = (NVRAM.len() + 3) / 4 * 4; @@ -675,7 +672,7 @@ where } pub async fn run(mut self) -> ! { - let mut buf = [0; 2048]; + let mut buf = [0; 512]; loop { // Send stuff // TODO flow control @@ -707,14 +704,8 @@ where .transaction(|bus| { let bus = unsafe { &mut *bus }; async { - bus.write(&cmd.to_le_bytes()).await?; - bus.read(&mut buf[..len as usize]).await?; - // pad to 32bit - let mut junk = [0; 4]; - if len % 4 != 0 { - bus.read(&mut junk[..(4 - len as usize % 4)]).await?; - } - + bus.write(&[cmd]).await?; + bus.read(&mut buf[..(len as usize + 3) / 4]).await?; Ok(()) } }) @@ -723,7 +714,7 @@ where trace!("rx {:02x}", &buf[..(len as usize).min(48)]); - self.rx(&buf[..len as usize]); + self.rx(&slice8_mut(&mut buf)[..len as usize]); } } @@ -735,7 +726,8 @@ where async fn send_packet(&mut self, packet: &[u8]) { trace!("tx pkt {:02x}", &packet[..packet.len().min(48)]); - let mut buf = [0; 2048]; + let mut buf = [0; 512]; + let buf8 = slice8_mut(&mut buf); let total_len = SdpcmHeader::SIZE + BcdHeader::SIZE + packet.len(); @@ -763,21 +755,21 @@ where trace!("tx {:?}", sdpcm_header); trace!(" {:?}", bcd_header); - buf[0..SdpcmHeader::SIZE].copy_from_slice(&sdpcm_header.to_bytes()); - buf[SdpcmHeader::SIZE..][..BcdHeader::SIZE].copy_from_slice(&bcd_header.to_bytes()); - buf[SdpcmHeader::SIZE + BcdHeader::SIZE..][..packet.len()].copy_from_slice(packet); + buf8[0..SdpcmHeader::SIZE].copy_from_slice(&sdpcm_header.to_bytes()); + buf8[SdpcmHeader::SIZE..][..BcdHeader::SIZE].copy_from_slice(&bcd_header.to_bytes()); + buf8[SdpcmHeader::SIZE + BcdHeader::SIZE..][..packet.len()].copy_from_slice(packet); let total_len = (total_len + 3) & !3; // round up to 4byte - trace!(" {:02x}", &buf[..total_len.min(48)]); + trace!(" {:02x}", &buf8[..total_len.min(48)]); let cmd = cmd_word(true, true, FUNC_WLAN, 0, total_len as _); self.spi .transaction(|bus| { let bus = unsafe { &mut *bus }; async { - bus.write(&cmd.to_le_bytes()).await?; - bus.write(&buf[..total_len]).await?; + bus.write(&[cmd]).await?; + bus.write(&buf[..(total_len + 3 / 4)]).await?; Ok(()) } }) @@ -875,7 +867,8 @@ where } async fn send_ioctl(&mut self, kind: u32, cmd: u32, iface: u32, data: &[u8], id: u16) { - let mut buf = [0; 2048]; + let mut buf = [0; 512]; + let buf8 = slice8_mut(&mut buf); let total_len = SdpcmHeader::SIZE + CdcHeader::SIZE + data.len(); @@ -904,13 +897,13 @@ where trace!("tx {:?}", sdpcm_header); trace!(" {:?}", cdc_header); - buf[0..SdpcmHeader::SIZE].copy_from_slice(&sdpcm_header.to_bytes()); - buf[SdpcmHeader::SIZE..][..CdcHeader::SIZE].copy_from_slice(&cdc_header.to_bytes()); - buf[SdpcmHeader::SIZE + CdcHeader::SIZE..][..data.len()].copy_from_slice(data); + buf8[0..SdpcmHeader::SIZE].copy_from_slice(&sdpcm_header.to_bytes()); + buf8[SdpcmHeader::SIZE..][..CdcHeader::SIZE].copy_from_slice(&cdc_header.to_bytes()); + buf8[SdpcmHeader::SIZE + CdcHeader::SIZE..][..data.len()].copy_from_slice(data); let total_len = (total_len + 3) & !3; // round up to 4byte - trace!(" {:02x}", &buf[..total_len.min(48)]); + trace!(" {:02x}", &buf8[..total_len.min(48)]); let cmd = cmd_word(true, true, FUNC_WLAN, 0, total_len as _); @@ -918,8 +911,8 @@ where .transaction(|bus| { let bus = unsafe { &mut *bus }; async { - bus.write(&cmd.to_le_bytes()).await?; - bus.write(&buf[..total_len]).await?; + bus.write(&[cmd]).await?; + bus.write(&buf[..(total_len + 3) / 4]).await?; Ok(()) } }) @@ -984,7 +977,7 @@ where true } - async fn bp_read(&mut self, mut addr: u32, mut data: &mut [u8]) { + async fn bp_read(&mut self, mut addr: u32, mut data: &mut [u32]) { // It seems the HW force-aligns the addr // to 2 if data.len() >= 2 // to 4 if data.len() >= 4 @@ -1006,19 +999,14 @@ where .transaction(|bus| { let bus = unsafe { &mut *bus }; async { - bus.write(&cmd.to_le_bytes()).await?; + bus.write(&[cmd]).await?; // 4-byte response delay. - let mut junk = [0; 4]; + let mut junk = [0; 1]; bus.read(&mut junk).await?; // Read data - bus.read(&mut data[..len]).await?; - - // pad to 32bit - if len % 4 != 0 { - bus.read(&mut junk[..(4 - len % 4)]).await?; - } + bus.read(&mut data[..len / 4]).await?; Ok(()) } }) @@ -1027,7 +1015,7 @@ where // Advance ptr. addr += len as u32; - data = &mut data[len..]; + data = &mut data[len / 4..]; } } @@ -1038,12 +1026,15 @@ where // To simplify, enforce 4-align for now. assert!(addr % 4 == 0); + let mut buf = [0u32; BACKPLANE_MAX_TRANSFER_SIZE / 4]; + while !data.is_empty() { // Ensure transfer doesn't cross a window boundary. let window_offs = addr & BACKPLANE_ADDRESS_MASK; let window_remaining = BACKPLANE_WINDOW_SIZE - window_offs as usize; let len = data.len().min(BACKPLANE_MAX_TRANSFER_SIZE).min(window_remaining); + slice8_mut(&mut buf)[..len].copy_from_slice(&data[..len]); self.backplane_set_window(addr).await; @@ -1053,13 +1044,8 @@ where .transaction(|bus| { let bus = unsafe { &mut *bus }; async { - bus.write(&cmd.to_le_bytes()).await?; - bus.write(&data[..len]).await?; - // pad to 32bit - if len % 4 != 0 { - let zeros = [0; 4]; - bus.write(&zeros[..(4 - len % 4)]).await?; - } + bus.write(&[cmd]).await?; + bus.write(&buf[..(len + 3) / 4]).await?; Ok(()) } }) @@ -1172,13 +1158,13 @@ where async fn readn(&mut self, func: u32, addr: u32, len: u32) -> u32 { let cmd = cmd_word(false, true, func, addr, len); - let mut buf = [0; 4]; + let mut buf = [0; 1]; self.spi .transaction(|bus| { let bus = unsafe { &mut *bus }; async { - bus.write(&cmd.to_le_bytes()).await?; + bus.write(&[cmd]).await?; if func == FUNC_BACKPLANE { // 4-byte response delay. bus.read(&mut buf).await?; @@ -1190,7 +1176,7 @@ where .await .unwrap(); - u32::from_le_bytes(buf) + buf[0] } async fn writen(&mut self, func: u32, addr: u32, val: u32, len: u32) { @@ -1200,8 +1186,7 @@ where .transaction(|bus| { let bus = unsafe { &mut *bus }; async { - bus.write(&cmd.to_le_bytes()).await?; - bus.write(&val.to_le_bytes()).await?; + bus.write(&[cmd, val]).await?; Ok(()) } }) @@ -1211,13 +1196,13 @@ where async fn read32_swapped(&mut self, addr: u32) -> u32 { let cmd = cmd_word(false, true, FUNC_BUS, addr, 4); - let mut buf = [0; 4]; + let mut buf = [0; 1]; self.spi .transaction(|bus| { let bus = unsafe { &mut *bus }; async { - bus.write(&swap16(cmd).to_le_bytes()).await?; + bus.write(&[swap16(cmd)]).await?; bus.read(&mut buf).await?; Ok(()) } @@ -1225,7 +1210,7 @@ where .await .unwrap(); - swap16(u32::from_le_bytes(buf)) + swap16(buf[0]) } async fn write32_swapped(&mut self, addr: u32, val: u32) { @@ -1235,8 +1220,7 @@ where .transaction(|bus| { let bus = unsafe { &mut *bus }; async { - bus.write(&swap16(cmd).to_le_bytes()).await?; - bus.write(&swap16(val).to_le_bytes()).await?; + bus.write(&[swap16(cmd), swap16(val)]).await?; Ok(()) } }) From 4205eef3ec46840fff77eb45cdadcbd51938b798 Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Sat, 16 Jul 2022 19:25:35 +0200 Subject: [PATCH 014/178] Fix iovar_get, unhardcode MAC addr. --- src/lib.rs | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 8c235b17..e42bae68 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -11,6 +11,7 @@ mod events; mod structs; use core::cell::Cell; +use core::cmp::{max, min}; use core::slice; use core::sync::atomic::Ordering; use core::task::Waker; @@ -272,17 +273,12 @@ impl<'a> Control<'a> { self.set_iovar_u32("bus:txglom", 0).await; self.set_iovar_u32("apsta", 1).await; - //self.set_iovar("cur_etheraddr", &[02, 03, 04, 05, 06, 07]).await; // read MAC addr. let mut mac_addr = [0; 6]; assert_eq!(self.get_iovar("cur_etheraddr", &mut mac_addr).await, 6); info!("mac addr: {:02x}", mac_addr); - // TODO get_iovar is broken, it returns all zeros. - // Harcdode our own MAC for now. - let mac_addr = [0x28, 0xCD, 0xC1, 0x00, 0x3F, 0x05]; - let country = countries::WORLD_WIDE_XX; let country_info = CountryInfo { country_abbrev: [country.code[0], country.code[1], 0, 0], @@ -439,10 +435,12 @@ impl<'a> Control<'a> { buf[..name.len()].copy_from_slice(name.as_bytes()); buf[name.len()] = 0; - let total_len = name.len() + 1 + res.len(); - let res_len = self.ioctl(0, 262, 0, &mut buf[..total_len]).await - name.len() - 1; - res[..res_len].copy_from_slice(&buf[name.len() + 1..][..res_len]); - res_len + let total_len = max(name.len() + 1, res.len()); + let res_len = self.ioctl(0, 262, 0, &mut buf[..total_len]).await; + + let out_len = min(res.len(), res_len); + res[..out_len].copy_from_slice(&buf[..out_len]); + out_len } async fn ioctl_set_u32(&mut self, cmd: u32, iface: u32, val: u32) { @@ -712,7 +710,7 @@ where .await .unwrap(); - trace!("rx {:02x}", &buf[..(len as usize).min(48)]); + trace!("rx {:02x}", &slice8_mut(&mut buf)[..(len as usize).min(48)]); self.rx(&slice8_mut(&mut buf)[..len as usize]); } From 13c88a9ca3e4a192677a184baa7e6ac12646797d Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Sun, 17 Jul 2022 00:33:30 +0200 Subject: [PATCH 015/178] Obtain the firmware blobs from the user instead of hardcoding magic flash addrs. --- .gitignore | 2 - examples/rpi-pico-w/src/main.rs | 15 +++++- firmware/43439A0.bin | Bin 0 -> 224190 bytes firmware/43439A0_clm.bin | Bin 0 -> 4752 bytes .../LICENSE-permissive-binary-license-1.0.txt | 49 ++++++++++++++++++ firmware/README.md | 5 ++ src/lib.rs | 26 ++++------ 7 files changed, 77 insertions(+), 20 deletions(-) create mode 100755 firmware/43439A0.bin create mode 100755 firmware/43439A0_clm.bin create mode 100644 firmware/LICENSE-permissive-binary-license-1.0.txt create mode 100644 firmware/README.md diff --git a/.gitignore b/.gitignore index ee8e6b4c..1e7caa9e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,2 @@ Cargo.lock target/ -*.bin -notes.txt diff --git a/examples/rpi-pico-w/src/main.rs b/examples/rpi-pico-w/src/main.rs index 475c6906..633c1b2b 100644 --- a/examples/rpi-pico-w/src/main.rs +++ b/examples/rpi-pico-w/src/main.rs @@ -43,6 +43,17 @@ async fn net_task(stack: &'static Stack>) -> ! { async fn main(spawner: Spawner, p: Peripherals) { info!("Hello World!"); + // Include the WiFi firmware and CLM. + let fw = include_bytes!("../../../firmware/43439A0.bin"); + let clm = include_bytes!("../../../firmware/43439A0_clm.bin"); + + // To make flashing faster for development, you may want to flash the firmwares independently + // at hardcoded addresses, instead of baking them into the program with `include_bytes!`: + // probe-rs-cli download 43439A0.bin --format bin --chip RP2040 --base-address 0x10100000 + // probe-rs-cli download 43439A0.clm_blob --format bin --chip RP2040 --base-address 0x10140000 + //let fw = unsafe { core::slice::from_raw_parts(0x10100000 as *const u8, 224190) }; + //let clm = unsafe { core::slice::from_raw_parts(0x10140000 as *const u8, 4752) }; + let pwr = Output::new(p.PIN_23, Level::Low); let cs = Output::new(p.PIN_25, Level::High); let clk = Output::new(p.PIN_29, Level::Low); @@ -54,11 +65,11 @@ async fn main(spawner: Spawner, p: Peripherals) { let spi = ExclusiveDevice::new(bus, cs); let state = forever!(cyw43::State::new()); - let (mut control, runner) = cyw43::new(state, pwr, spi).await; + let (mut control, runner) = cyw43::new(state, pwr, spi, fw).await; spawner.spawn(wifi_task(runner)).unwrap(); - let net_device = control.init().await; + let net_device = control.init(clm).await; //control.join_open("MikroTik-951589").await; control.join_wpa2("DirbaioWifi", "HelloWorld").await; diff --git a/firmware/43439A0.bin b/firmware/43439A0.bin new file mode 100755 index 0000000000000000000000000000000000000000..b46b3beff05de7dfcbf41d53a12bf3a74381881b GIT binary patch literal 224190 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%0DkO7rHTf}Q17Z4ste}do4Nrd>hUFkyZ${+KA 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

1d=ZlpzNk=JW(zlukh-yUe%sMBuCzTL5c8(n(5 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≫q$>JSAzW#r380%0>2$qMp~GuIgVDDg#c#<>v{ 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 C^%uGT literal 0 HcmV?d00001 diff --git a/firmware/43439A0_clm.bin b/firmware/43439A0_clm.bin new file mode 100755 index 0000000000000000000000000000000000000000..6e3ba786b2b496ef3d347d8f8150cb433f684692 GIT binary patch literal 4752 zcmd5=&2Jo85wG`p#$%7i<96cRusN(+3P=bXGc$G^+e9l*_k4HvxINu7p0RR?hy({h zP{bjpAntoY95`}CLIUx3gTWD{Ngg`9-o{YpXg-u9lm+};^mKCzk1v`?tGju z#x%{YX_-CKw$?W7u5Ecc&F1cItJT_TwfFYg?VB#$^qA!@2Si6~`O}pFMxf_WY1-HEpv$?9jST>m~JvroS-# z!t|Fm9GY-!!nuk1Hkz1dW1>4V7OTpeSlh%JosDYhDHoVIZQtQvjtjK%Upyw^K`Zzoyk4?!M zUeaPAo~@PUux3qC!yXgTST^;r#=2_ckuisLTZh`38MVBm?VTMMH%wd4D$Q5hUZ-u2 z+TPWN8Sl#TuIlF4_nchvt}MAl%KHU*VGUM)(elE!_dW2k-nz9#&70RNhaFn7Lu;}yG#Cukq_AP=P=c>b!tjmWgYB^j-*VmK9G731TO7i`2fVNvaeO(op6N!2%E!@3E&C=hes$>IK10sjll6kg~L@qrY6cYWrDro zhcicX!bFsYsn{F>Dg4Y8Dc7y8Sv!D!1QZqfMzw30xV*Zlfd+-Et&G6W$d&;t1Upw@Atbf@fw|o}!6L=5x_W zo=9TT1W5v+E&3--0M#j*A&_Td8!fQBU$7~ z1Oq;y-N+0{;w##iSmij}9)Ky9}dlNl464@Zc-A^TllGw$I5=4i0oS#T^ zDAAyBpX8J#nk3vd$p}gc(uTVzA&`grEGLB-EHL2iN(kBSl#0iKR6Ukr6Crz3t%vDE zvNzS*k|NoV>Z!Ueh3j}b1gS)j(oE`nimbz~OeN>Ym& zQ&9%zT28Ow4TK=;%ht=(tC^lM8MMP2nVvEkyb9N4Y6f?`B1heu%_RicLaI$Bg@`is zAvemxB?qFP$x%YwKs%*;C$Xhv?YNnslc5_au$_gNN(;r#J+~9M&?>PI$i%(4bLK*d zUMH7S4|1eB+>&dm=g2XHJ6CUTAMYgRV{ufjIhJcsxY3t2Gr^u~`tb!orZf+8bySW# zLSf;XgnNz*2 zag_OT(kh!3@sPWI5F2TzmMUi@5u}v{Zlx))(xhEYMe<5e&tVEyr4cJn{C;wX|r8B0moI19|<0yYR|}CZQG+g zd*t(_>B_Um)wi`r{E7qXzVWEotSKPinw8tPHT(8}Z+s#-!^9eFFDo6qgzsSXYR8_l zV8ROpzMB#Knjaiu`T^7b;(Hkfm+-3P1=IXTf8+Nf-0+ge^qL2_HJ~Qb-@Qki*G|Tl z{JTZQdbRpiV|%B$%Qr#mZMXK?`~Jbb``bDU8GqpSCfw6-H^Y6*?1W~gG;&16< literal 0 HcmV?d00001 diff --git a/firmware/LICENSE-permissive-binary-license-1.0.txt b/firmware/LICENSE-permissive-binary-license-1.0.txt new file mode 100644 index 00000000..cbb51f9c --- /dev/null +++ b/firmware/LICENSE-permissive-binary-license-1.0.txt @@ -0,0 +1,49 @@ +Permissive Binary License + +Version 1.0, July 2019 + +Redistribution. Redistribution and use in binary form, without +modification, are permitted provided that the following conditions are +met: + +1) Redistributions must reproduce the above copyright notice and the + following disclaimer in the documentation and/or other materials + provided with the distribution. + +2) Unless to the extent explicitly permitted by law, no reverse + engineering, decompilation, or disassembly of this software is + permitted. + +3) Redistribution as part of a software development kit must include the + accompanying file named �DEPENDENCIES� and any dependencies listed in + that file. + +4) Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +Limited patent license. The copyright holders (and contributors) grant a +worldwide, non-exclusive, no-charge, royalty-free patent license to +make, have made, use, offer to sell, sell, import, and otherwise +transfer this software, where such license applies only to those patent +claims licensable by the copyright holders (and contributors) that are +necessarily infringed by this software. This patent license shall not +apply to any combinations that include this software. No hardware is +licensed hereunder. + +If you institute patent litigation against any entity (including a +cross-claim or counterclaim in a lawsuit) alleging that the software +itself infringes your patent(s), then your rights granted under this +license shall terminate as of the date such litigation is filed. + +DISCLAIMER. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND +CONTRIBUTORS "AS IS." ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT +NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED +TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/firmware/README.md b/firmware/README.md new file mode 100644 index 00000000..7381fdc5 --- /dev/null +++ b/firmware/README.md @@ -0,0 +1,5 @@ +# WiFi firmware + +Firmware obtained from https://github.com/Infineon/wifi-host-driver/tree/master/WiFi_Host_Driver/resources/firmware/COMPONENT_43439 + +Licensed under the [Infineon Permissive Binary License](./LICENSE-permissive-binary-license-1.0.txt) \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index e42bae68..0e4a862c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -235,11 +235,9 @@ pub struct Control<'a> { } impl<'a> Control<'a> { - pub async fn init(&mut self) -> NetDevice<'a> { + pub async fn init(&mut self, clm: &[u8]) -> NetDevice<'a> { const CHUNK_SIZE: usize = 1024; - let clm = unsafe { slice::from_raw_parts(0x10140000 as *const u8, 4752) }; - info!("Downloading CLM..."); let mut offs = 0; @@ -528,7 +526,12 @@ pub struct Runner<'a, PWR, SPI> { backplane_window: u32, } -pub async fn new<'a, PWR, SPI>(state: &'a State, pwr: PWR, spi: SPI) -> (Control<'a>, Runner<'a, PWR, SPI>) +pub async fn new<'a, PWR, SPI>( + state: &'a State, + pwr: PWR, + spi: SPI, + firmware: &[u8], +) -> (Control<'a>, Runner<'a, PWR, SPI>) where PWR: OutputPin, SPI: SpiDevice, @@ -543,7 +546,7 @@ where backplane_window: 0xAAAA_AAAA, }; - runner.init().await; + runner.init(firmware).await; (Control { state }, runner) } @@ -554,7 +557,7 @@ where SPI: SpiDevice, SPI::Bus: SpiBusRead + SpiBusWrite, { - async fn init(&mut self) { + async fn init(&mut self, firmware: &[u8]) { // Reset self.pwr.set_low().unwrap(); Timer::after(Duration::from_millis(20)).await; @@ -598,17 +601,8 @@ where let ram_addr = CHIP.atcm_ram_base_address; - // I'm flashing the firmwares independently at hardcoded addresses, instead of baking them - // into the program with `include_bytes!` or similar, so that flashing the program stays fast. - // - // Flash them like this, also don't forget to update the lengths below if you change them!. - // - // probe-rs-cli download 43439A0.bin --format bin --chip RP2040 --base-address 0x10100000 - // probe-rs-cli download 43439A0.clm_blob --format bin --chip RP2040 --base-address 0x10140000 - let fw = unsafe { slice::from_raw_parts(0x10100000 as *const u8, 224190) }; - info!("loading fw"); - self.bp_write(ram_addr, fw).await; + self.bp_write(ram_addr, firmware).await; info!("loading nvram"); // Round up to 4 bytes. From 54269a07614105b30e8ea52d424ff417fd9e6e87 Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Sun, 17 Jul 2022 00:34:27 +0200 Subject: [PATCH 016/178] Switch default log to debug. Trace is very VRYY verbose. --- examples/rpi-pico-w/.cargo/config.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/rpi-pico-w/.cargo/config.toml b/examples/rpi-pico-w/.cargo/config.toml index 18bd4dfe..6183e70f 100644 --- a/examples/rpi-pico-w/.cargo/config.toml +++ b/examples/rpi-pico-w/.cargo/config.toml @@ -5,4 +5,4 @@ runner = "probe-run --chip RP2040" target = "thumbv6m-none-eabi" [env] -DEFMT_LOG = "trace" +DEFMT_LOG = "debug" From 726d68a706507d3c0a1b7c5a9c76231ccf1dcbcf Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Sun, 17 Jul 2022 00:34:41 +0200 Subject: [PATCH 017/178] Add status and instructions in README. --- README.md | 45 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 7df988b7..8ee0c235 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,49 @@ # cyw43 -Very WIP driver for the CYW43439 wifi chip, used in the Raspberry Pi Pico W. +WIP driver for the CYW43439 wifi chip, used in the Raspberry Pi Pico W. Implementation based on [Infineon/wifi-host-driver](https://github.com/Infineon/wifi-host-driver). + +## Current status + +Working: + +- Station mode (joining an AP). +- Sending and receiving Ethernet frames. +- Using the default MAC address. +- [`embassy-net`](https://embassy.dev) integration. + +TODO: + +- AP mode (creating an AP) +- GPIO support (used for the Pico W LED) +- Scanning +- Setting a custom MAC address. +- RP2040 PIO driver for the nonstandard half-duplex SPI used in the Pico W. Probably porting [this](https://github.com/raspberrypi/pico-sdk/tree/master/src/rp2_common/cyw43_driver). (Currently bitbanging is used). +- Using the IRQ pin instead of polling the bus. +- Bus sleep (unclear what the benefit is. Is it needed for IRQs? or is it just power consumption optimization?) + +## Running the example + +- `cargo install probe-run` +- `cd examples/rpi-pico-w` +- Edit `src/main.rs` with your Wifi network's name and password. +- `cargo run --release` + +After a few seconds, you should see that DHCP picks up an IP address like this + +``` +11.944489 DEBUG Acquired IP configuration: +11.944517 DEBUG IP address: 192.168.0.250/24 +11.944620 DEBUG Default gateway: 192.168.0.33 +11.944722 DEBUG DNS server 0: 192.168.0.33 +``` + +The example implements a TCP echo server on port 1234. You can try connecting to it with: + +``` +nc 192.168.0.250 1234 +``` + +Send it some data, you should see it echoed back and printed in the firmware's logs. ## License From 92505f53e239bf6004dec1140b2d5a15b762bb66 Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Thu, 21 Jul 2022 23:50:40 +0200 Subject: [PATCH 018/178] Get wifi credentials from envvars in example. --- .vscode/settings.json | 4 ++++ README.md | 3 +-- examples/rpi-pico-w/src/main.rs | 4 ++-- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 748816bb..082b286d 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -12,4 +12,8 @@ "rust-analyzer.linkedProjects": [ "examples/rpi-pico-w/Cargo.toml", ], + "rust-analyzer.server.extraEnv": { + "WIFI_NETWORK": "foo", + "WIFI_PASSWORD": "foo", + } } \ No newline at end of file diff --git a/README.md b/README.md index 8ee0c235..5d4347e9 100644 --- a/README.md +++ b/README.md @@ -25,8 +25,7 @@ TODO: - `cargo install probe-run` - `cd examples/rpi-pico-w` -- Edit `src/main.rs` with your Wifi network's name and password. -- `cargo run --release` +- `WIFI_NETWORK=MyWifiNetwork WIFI_PASSWORD=MyWifiPassword cargo run --release` After a few seconds, you should see that DHCP picks up an IP address like this diff --git a/examples/rpi-pico-w/src/main.rs b/examples/rpi-pico-w/src/main.rs index 633c1b2b..3e966d21 100644 --- a/examples/rpi-pico-w/src/main.rs +++ b/examples/rpi-pico-w/src/main.rs @@ -71,8 +71,8 @@ async fn main(spawner: Spawner, p: Peripherals) { let net_device = control.init(clm).await; - //control.join_open("MikroTik-951589").await; - control.join_wpa2("DirbaioWifi", "HelloWorld").await; + //control.join_open(env!("WIFI_NETWORK")).await; + control.join_wpa2(env!("WIFI_NETWORK"), env!("WIFI_PASSWORD")).await; let config = embassy_net::ConfigStrategy::Dhcp; //let config = embassy_net::ConfigStrategy::Static(embassy_net::Config { From 5ef40acd1d9bf3b9c94787f3901ef32bd0d3a248 Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Fri, 22 Jul 2022 00:05:39 +0200 Subject: [PATCH 019/178] Fix set iovar buffer length. --- src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 0e4a862c..b06eb36e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -422,7 +422,7 @@ impl<'a> Control<'a> { buf[name.len() + 1..][..val.len()].copy_from_slice(val); let total_len = name.len() + 1 + val.len(); - self.ioctl(2, 263, 0, &mut buf).await; + self.ioctl(2, 263, 0, &mut buf[..total_len]).await; } // TODO this is not really working, it always returns all zeros. @@ -904,7 +904,7 @@ where let bus = unsafe { &mut *bus }; async { bus.write(&[cmd]).await?; - bus.write(&buf[..(total_len + 3) / 4]).await?; + bus.write(&buf[..total_len / 4]).await?; Ok(()) } }) From ddfbfa0132285963382a4d7290d506186da31369 Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Thu, 28 Jul 2022 18:43:17 +0200 Subject: [PATCH 020/178] move ioctl_id from State to Runner. --- src/lib.rs | 30 ++++++++++++++---------------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index b06eb36e..133ce316 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -209,7 +209,6 @@ enum IoctlState { } pub struct State { - ioctl_id: Cell, ioctl_state: Cell, tx_channel: Channel, @@ -220,7 +219,6 @@ pub struct State { impl State { pub fn new() -> Self { Self { - ioctl_id: Cell::new(0), ioctl_state: Cell::new(IoctlState::Idle), tx_channel: Channel::new(), @@ -453,8 +451,6 @@ impl<'a> Control<'a> { yield_now().await; } - self.state.ioctl_id.set(self.state.ioctl_id.get().wrapping_add(1)); - self.state .ioctl_state .set(IoctlState::Pending { kind, cmd, iface, buf }); @@ -522,7 +518,8 @@ pub struct Runner<'a, PWR, SPI> { pwr: PWR, spi: SPI, - ioctl_seq: u8, + ioctl_id: u16, + sdpcm_seq: u8, backplane_window: u32, } @@ -542,7 +539,8 @@ where pwr, spi, - ioctl_seq: 0, + ioctl_id: 0, + sdpcm_seq: 0, backplane_window: 0xAAAA_AAAA, }; @@ -669,8 +667,7 @@ where // Send stuff // TODO flow control if let IoctlState::Pending { kind, cmd, iface, buf } = self.state.ioctl_state.get() { - self.send_ioctl(kind, cmd, iface, unsafe { &*buf }, self.state.ioctl_id.get()) - .await; + self.send_ioctl(kind, cmd, iface, unsafe { &*buf }).await; self.state.ioctl_state.set(IoctlState::Sent { buf }); } @@ -723,8 +720,8 @@ where let total_len = SdpcmHeader::SIZE + BcdHeader::SIZE + packet.len(); - let seq = self.ioctl_seq; - self.ioctl_seq = self.ioctl_seq.wrapping_add(1); + let seq = self.sdpcm_seq; + self.sdpcm_seq = self.sdpcm_seq.wrapping_add(1); let sdpcm_header = SdpcmHeader { len: total_len as u16, // TODO does this len need to be rounded up to u32? @@ -802,7 +799,7 @@ where trace!(" {:?}", cdc_header); if let IoctlState::Sent { buf } = self.state.ioctl_state.get() { - if cdc_header.id == self.state.ioctl_id.get() { + if cdc_header.id == self.ioctl_id { assert_eq!(cdc_header.status, 0); // todo propagate error instead let resp_len = cdc_header.len as usize; @@ -858,19 +855,20 @@ where } } - async fn send_ioctl(&mut self, kind: u32, cmd: u32, iface: u32, data: &[u8], id: u16) { + async fn send_ioctl(&mut self, kind: u32, cmd: u32, iface: u32, data: &[u8]) { let mut buf = [0; 512]; let buf8 = slice8_mut(&mut buf); let total_len = SdpcmHeader::SIZE + CdcHeader::SIZE + data.len(); - let seq = self.ioctl_seq; - self.ioctl_seq = self.ioctl_seq.wrapping_add(1); + let sdpcm_seq = self.sdpcm_seq; + self.sdpcm_seq = self.sdpcm_seq.wrapping_add(1); + self.ioctl_id = self.ioctl_id.wrapping_add(1); let sdpcm_header = SdpcmHeader { len: total_len as u16, // TODO does this len need to be rounded up to u32? len_inv: !total_len as u16, - sequence: seq, + sequence: sdpcm_seq, channel_and_flags: 0, // control channel next_length: 0, header_length: SdpcmHeader::SIZE as _, @@ -883,7 +881,7 @@ where cmd: cmd, len: data.len() as _, flags: kind as u16 | (iface as u16) << 12, - id, + id: self.ioctl_id, status: 0, }; trace!("tx {:?}", sdpcm_header); From 3388b5cecf95d6b0bc9cf5c952e9f0aa1e019b8a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mattias=20Gr=C3=B6nlund?= Date: Tue, 9 Aug 2022 00:53:32 +0200 Subject: [PATCH 021/178] Improve data checks for VHD events For some reason I got strange events on channel 1 (ASYNCEVENT_HEADER): 0.647329 WARN unexpected ehternet type 0x0508, expected Qualcom ether type 0x886c This patch improves the validation of BCD WHD events to minimize the risk for panic. --- src/events.rs | 1 + src/lib.rs | 53 +++++++++++++++++++++++++++++++++++++++-------- src/structs.rs | 56 ++++++++++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 99 insertions(+), 11 deletions(-) diff --git a/src/events.rs b/src/events.rs index b35b12fa..a828eec9 100644 --- a/src/events.rs +++ b/src/events.rs @@ -1,4 +1,5 @@ #![allow(unused)] +#![allow(non_camel_case_types)] use core::num; diff --git a/src/lib.rs b/src/lib.rs index 133ce316..3d08370c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -815,20 +815,55 @@ where trace!(" {:?}", bcd_header); let packet_start = BcdHeader::SIZE + 4 * bcd_header.data_offset as usize; - if packet_start > payload.len() { - warn!("packet start out of range."); + + if packet_start + EventPacket::SIZE > payload.len() { + warn!("BCD event, incomplete header"); return; } - let packet = &payload[packet_start..]; - trace!(" {:02x}", &packet[..(packet.len() as usize).min(36)]); + let bcd_packet = &payload[packet_start..]; + trace!(" {:02x}", &bcd_packet[..(bcd_packet.len() as usize).min(36)]); - let mut evt = EventHeader::from_bytes(&packet[24..][..EventHeader::SIZE].try_into().unwrap()); - evt.byteswap(); - let evt_data = &packet[24 + EventHeader::SIZE..][..evt.datalen as usize]; + let mut event_packet = EventPacket::from_bytes(&bcd_packet[..EventPacket::SIZE].try_into().unwrap()); + event_packet.byteswap(); + + const ETH_P_LINK_CTL: u16 = 0x886c; // HPNA, wlan link local tunnel, according to linux if_ether.h + if event_packet.eth.ether_type != ETH_P_LINK_CTL { + warn!( + "unexpected ethernet type 0x{:04x}, expected Broadcom ether type 0x{:04x}", + event_packet.eth.ether_type, ETH_P_LINK_CTL + ); + return; + } + const BROADCOM_OUI: &[u8] = &[0x00, 0x10, 0x18]; + if event_packet.hdr.oui != BROADCOM_OUI { + warn!( + "unexpected ethernet OUI {:02x}, expected Broadcom OUI {:02x}", + event_packet.hdr.oui, BROADCOM_OUI + ); + return; + } + const BCMILCP_SUBTYPE_VENDOR_LONG: u16 = 32769; + if event_packet.hdr.subtype != BCMILCP_SUBTYPE_VENDOR_LONG { + warn!("unexpected subtype {}", event_packet.hdr.subtype); + return; + } + + const BCMILCP_BCM_SUBTYPE_EVENT: u16 = 1; + if event_packet.hdr.user_subtype != BCMILCP_BCM_SUBTYPE_EVENT { + warn!("unexpected user_subtype {}", event_packet.hdr.subtype); + return; + } + + if event_packet.msg.datalen as usize >= (bcd_packet.len() - EventMessage::SIZE) { + warn!("BCD event, incomplete data"); + return; + } + + let evt_data = &bcd_packet[EventMessage::SIZE..][..event_packet.msg.datalen as usize]; debug!( "=== EVENT {}: {} {:02x}", - events::Event::from(evt.event_type as u8), - evt, + events::Event::from(event_packet.msg.event_type as u8), + event_packet.msg, evt_data ); } diff --git a/src/structs.rs b/src/structs.rs index 060c2b06..7a7c25b2 100644 --- a/src/structs.rs +++ b/src/structs.rs @@ -64,10 +64,44 @@ pub struct BcdHeader { } impl_bytes!(BcdHeader); +#[derive(Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[repr(C)] +pub struct EthernetHeader { + pub destination_mac: [u8; 6], + pub source_mac: [u8; 6], + pub ether_type: u16, +} + +impl EthernetHeader { + pub fn byteswap(&mut self) { + self.ether_type = self.ether_type.to_be(); + } +} + #[derive(Clone, Copy)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] #[repr(C)] pub struct EventHeader { + pub subtype: u16, + pub length: u16, + pub version: u8, + pub oui: [u8; 3], + pub user_subtype: u16, +} + +impl EventHeader { + pub fn byteswap(&mut self) { + self.subtype = self.subtype.to_be(); + self.length = self.length.to_be(); + self.user_subtype = self.user_subtype.to_be(); + } +} + +#[derive(Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[repr(C)] +pub struct EventMessage { /// version pub version: u16, /// see flags below @@ -91,9 +125,9 @@ pub struct EventHeader { /// source bsscfg index pub bsscfgidx: u8, } -impl_bytes!(EventHeader); +impl_bytes!(EventMessage); -impl EventHeader { +impl EventMessage { pub fn byteswap(&mut self) { self.version = self.version.to_be(); self.flags = self.flags.to_be(); @@ -105,6 +139,24 @@ impl EventHeader { } } +#[derive(Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[repr(C)] +pub struct EventPacket { + pub eth: EthernetHeader, + pub hdr: EventHeader, + pub msg: EventMessage, +} +impl_bytes!(EventPacket); + +impl EventPacket { + pub fn byteswap(&mut self) { + self.eth.byteswap(); + self.hdr.byteswap(); + self.msg.byteswap(); + } +} + #[derive(Clone, Copy)] #[repr(C)] pub struct DownloadHeader { From f76815d642064b5ed5b1673e4a386e2747813f20 Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Sat, 13 Aug 2022 15:37:30 +0200 Subject: [PATCH 022/178] Update Embassy. --- Cargo.toml | 6 ++++-- examples/rpi-pico-w/Cargo.toml | 13 ++++++++----- examples/rpi-pico-w/src/main.rs | 17 ++++++++--------- src/lib.rs | 8 ++++---- 4 files changed, 24 insertions(+), 20 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index d35e865b..cea7d780 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,10 +4,12 @@ version = "0.1.0" edition = "2021" [features] -defmt = ["dep:defmt", "embassy/defmt"] +defmt = ["dep:defmt"] log = ["dep:log"] + [dependencies] -embassy = { version = "0.1.0" } +embassy-executor = { version = "0.1.0", features = [ "time" ] } +embassy-util = { version = "0.1.0" } embassy-net = { version = "0.1.0" } atomic-polyfill = "0.1.5" diff --git a/examples/rpi-pico-w/Cargo.toml b/examples/rpi-pico-w/Cargo.toml index 9e1d7547..af558d8c 100644 --- a/examples/rpi-pico-w/Cargo.toml +++ b/examples/rpi-pico-w/Cargo.toml @@ -6,7 +6,8 @@ edition = "2021" [dependencies] cyw43 = { path = "../../", features = ["defmt"]} -embassy = { version = "0.1.0", features = ["defmt", "defmt-timestamp-uptime"] } +embassy-executor = { version = "0.1.0", features = ["defmt", "defmt-timestamp-uptime"] } +embassy-util = { version = "0.1.0" } embassy-rp = { version = "0.1.0", features = ["defmt", "unstable-traits", "nightly", "unstable-pac"] } embassy-net = { version = "0.1.0", features = ["defmt", "tcp", "dhcpv4", "medium-ethernet", "pool-16"] } atomic-polyfill = "0.1.5" @@ -26,10 +27,12 @@ heapless = "0.7.15" [patch.crates-io] -embassy = { git = "https://github.com/embassy-rs/embassy", rev = "5f43c1d37e9db847c7861fe0bd821db62edba9f6" } -embassy-rp = { git = "https://github.com/embassy-rs/embassy", rev = "5f43c1d37e9db847c7861fe0bd821db62edba9f6" } -embassy-net = { git = "https://github.com/embassy-rs/embassy", rev = "5f43c1d37e9db847c7861fe0bd821db62edba9f6" } -#embassy = { path = "/home/dirbaio/embassy/embassy/embassy" } +embassy-executor = { git = "https://github.com/embassy-rs/embassy", rev = "6ffca81a38d2c7f57da667ff49b4296c4eba78e2" } +embassy-util = { git = "https://github.com/embassy-rs/embassy", rev = "6ffca81a38d2c7f57da667ff49b4296c4eba78e2" } +embassy-rp = { git = "https://github.com/embassy-rs/embassy", rev = "6ffca81a38d2c7f57da667ff49b4296c4eba78e2" } +embassy-net = { git = "https://github.com/embassy-rs/embassy", rev = "6ffca81a38d2c7f57da667ff49b4296c4eba78e2" } +#embassy-executor = { path = "/home/dirbaio/embassy/embassy/embassy-executor" } +#embassy-util = { path = "/home/dirbaio/embassy/embassy/embassy-util" } #embassy-rp = { path = "/home/dirbaio/embassy/embassy/embassy-rp" } #embassy-net = { path = "/home/dirbaio/embassy/embassy/embassy-net" } #smoltcp = { path = "./smoltcp" } diff --git a/examples/rpi-pico-w/src/main.rs b/examples/rpi-pico-w/src/main.rs index 3e966d21..91f08726 100644 --- a/examples/rpi-pico-w/src/main.rs +++ b/examples/rpi-pico-w/src/main.rs @@ -5,15 +5,14 @@ use core::convert::Infallible; use core::future::Future; -use defmt::{assert, assert_eq, panic, *}; -use embassy::executor::Spawner; -use embassy::time::{Duration, Timer}; -use embassy::util::Forever; +use defmt::*; +use embassy_executor::executor::Spawner; use embassy_net::tcp::TcpSocket; -use embassy_net::{Ipv4Address, Ipv4Cidr, Stack, StackResources}; -use embassy_rp::gpio::{Flex, Level, Output, Pin}; +use embassy_net::{Stack, StackResources}; +use embassy_rp::gpio::{Flex, Level, Output}; use embassy_rp::peripherals::{PIN_23, PIN_24, PIN_25, PIN_29}; use embassy_rp::Peripherals; +use embassy_util::Forever; use embedded_hal_1::spi::ErrorType; use embedded_hal_async::spi::{ExclusiveDevice, SpiBusFlush, SpiBusRead, SpiBusWrite}; use embedded_io::asynch::{Read, Write}; @@ -27,19 +26,19 @@ macro_rules! forever { }}; } -#[embassy::task] +#[embassy_executor::task] async fn wifi_task( runner: cyw43::Runner<'static, Output<'static, PIN_23>, ExclusiveDevice>>, ) -> ! { runner.run().await } -#[embassy::task] +#[embassy_executor::task] async fn net_task(stack: &'static Stack>) -> ! { stack.run().await } -#[embassy::main] +#[embassy_executor::main] async fn main(spawner: Spawner, p: Peripherals) { info!("Hello World!"); diff --git a/src/lib.rs b/src/lib.rs index 3d08370c..3932ce41 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -17,11 +17,11 @@ use core::sync::atomic::Ordering; use core::task::Waker; use atomic_polyfill::AtomicBool; -use embassy::blocking_mutex::raw::NoopRawMutex; -use embassy::channel::mpmc::Channel; -use embassy::time::{block_for, Duration, Timer}; -use embassy::util::yield_now; +use embassy_executor::time::{block_for, Duration, Timer}; use embassy_net::{PacketBoxExt, PacketBuf}; +use embassy_util::blocking_mutex::raw::NoopRawMutex; +use embassy_util::channel::mpmc::Channel; +use embassy_util::yield_now; use embedded_hal_1::digital::blocking::OutputPin; use embedded_hal_async::spi::{SpiBusRead, SpiBusWrite, SpiDevice}; From 6b4555a6a70f8c10755198b69e7cf085c1a53fce Mon Sep 17 00:00:00 2001 From: Daniel Bevenius Date: Sat, 20 Aug 2022 10:49:27 +0200 Subject: [PATCH 023/178] Add comments about Country Locale Matrix (CLM) This commit add comments about what CLM stands for. The motivation of this is that I think it helps understanding the code for users who are new to the codebase (like me). --- examples/rpi-pico-w/src/main.rs | 2 +- src/structs.rs | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/examples/rpi-pico-w/src/main.rs b/examples/rpi-pico-w/src/main.rs index 91f08726..569c9bf4 100644 --- a/examples/rpi-pico-w/src/main.rs +++ b/examples/rpi-pico-w/src/main.rs @@ -42,7 +42,7 @@ async fn net_task(stack: &'static Stack>) -> ! { async fn main(spawner: Spawner, p: Peripherals) { info!("Hello World!"); - // Include the WiFi firmware and CLM. + // Include the WiFi firmware and Country Locale Matrix (CLM) blobs. let fw = include_bytes!("../../../firmware/43439A0.bin"); let clm = include_bytes!("../../../firmware/43439A0_clm.bin"); diff --git a/src/structs.rs b/src/structs.rs index 7a7c25b2..35547097 100644 --- a/src/structs.rs +++ b/src/structs.rs @@ -172,6 +172,7 @@ pub const DOWNLOAD_FLAG_BEGIN: u16 = 0x0002; pub const DOWNLOAD_FLAG_END: u16 = 0x0004; pub const DOWNLOAD_FLAG_HANDLER_VER: u16 = 0x1000; +// Country Locale Matrix (CLM) pub const DOWNLOAD_TYPE_CLM: u16 = 2; #[derive(Clone, Copy)] From 945449b10fe815dd10875f55482d4777d6d801b7 Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Mon, 22 Aug 2022 17:24:43 +0200 Subject: [PATCH 024/178] Update Embassy. --- Cargo.toml | 2 +- examples/rpi-pico-w/Cargo.toml | 15 +++++++++------ examples/rpi-pico-w/src/main.rs | 21 +++++++++++---------- src/lib.rs | 2 +- 4 files changed, 22 insertions(+), 18 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index cea7d780..c6120dac 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,7 +8,7 @@ defmt = ["dep:defmt"] log = ["dep:log"] [dependencies] -embassy-executor = { version = "0.1.0", features = [ "time" ] } +embassy-time = { version = "0.1.0" } embassy-util = { version = "0.1.0" } embassy-net = { version = "0.1.0" } atomic-polyfill = "0.1.5" diff --git a/examples/rpi-pico-w/Cargo.toml b/examples/rpi-pico-w/Cargo.toml index af558d8c..98a3d105 100644 --- a/examples/rpi-pico-w/Cargo.toml +++ b/examples/rpi-pico-w/Cargo.toml @@ -6,17 +6,19 @@ edition = "2021" [dependencies] cyw43 = { path = "../../", features = ["defmt"]} -embassy-executor = { version = "0.1.0", features = ["defmt", "defmt-timestamp-uptime"] } +embassy-executor = { version = "0.1.0", features = ["defmt", "integrated-timers"] } +embassy-time = { version = "0.1.0", features = ["defmt", "defmt-timestamp-uptime"] } embassy-util = { version = "0.1.0" } embassy-rp = { version = "0.1.0", features = ["defmt", "unstable-traits", "nightly", "unstable-pac"] } embassy-net = { version = "0.1.0", features = ["defmt", "tcp", "dhcpv4", "medium-ethernet", "pool-16"] } atomic-polyfill = "0.1.5" +static_cell = "1.0" defmt = "0.3" defmt-rtt = "0.3" panic-probe = { version = "0.3", features = ["print-defmt"] } -cortex-m = "0.7.3" +cortex-m = { version = "0.7.6", features = ["critical-section-single-core"]} cortex-m-rt = "0.7.0" futures = { version = "0.3.17", default-features = false, features = ["async-await", "cfg-target-has-atomic", "unstable"] } @@ -27,10 +29,11 @@ heapless = "0.7.15" [patch.crates-io] -embassy-executor = { git = "https://github.com/embassy-rs/embassy", rev = "6ffca81a38d2c7f57da667ff49b4296c4eba78e2" } -embassy-util = { git = "https://github.com/embassy-rs/embassy", rev = "6ffca81a38d2c7f57da667ff49b4296c4eba78e2" } -embassy-rp = { git = "https://github.com/embassy-rs/embassy", rev = "6ffca81a38d2c7f57da667ff49b4296c4eba78e2" } -embassy-net = { git = "https://github.com/embassy-rs/embassy", rev = "6ffca81a38d2c7f57da667ff49b4296c4eba78e2" } +embassy-executor = { git = "https://github.com/embassy-rs/embassy", rev = "53fbd0efb3e77e1e3de948afde2b5bf1a5a9735f" } +embassy-time = { git = "https://github.com/embassy-rs/embassy", rev = "53fbd0efb3e77e1e3de948afde2b5bf1a5a9735f" } +embassy-util = { git = "https://github.com/embassy-rs/embassy", rev = "53fbd0efb3e77e1e3de948afde2b5bf1a5a9735f" } +embassy-rp = { git = "https://github.com/embassy-rs/embassy", rev = "53fbd0efb3e77e1e3de948afde2b5bf1a5a9735f" } +embassy-net = { git = "https://github.com/embassy-rs/embassy", rev = "53fbd0efb3e77e1e3de948afde2b5bf1a5a9735f" } #embassy-executor = { path = "/home/dirbaio/embassy/embassy/embassy-executor" } #embassy-util = { path = "/home/dirbaio/embassy/embassy/embassy-util" } #embassy-rp = { path = "/home/dirbaio/embassy/embassy/embassy-rp" } diff --git a/examples/rpi-pico-w/src/main.rs b/examples/rpi-pico-w/src/main.rs index 569c9bf4..986474ce 100644 --- a/examples/rpi-pico-w/src/main.rs +++ b/examples/rpi-pico-w/src/main.rs @@ -6,23 +6,22 @@ use core::convert::Infallible; use core::future::Future; use defmt::*; -use embassy_executor::executor::Spawner; +use embassy_executor::Spawner; use embassy_net::tcp::TcpSocket; use embassy_net::{Stack, StackResources}; use embassy_rp::gpio::{Flex, Level, Output}; use embassy_rp::peripherals::{PIN_23, PIN_24, PIN_25, PIN_29}; -use embassy_rp::Peripherals; -use embassy_util::Forever; use embedded_hal_1::spi::ErrorType; use embedded_hal_async::spi::{ExclusiveDevice, SpiBusFlush, SpiBusRead, SpiBusWrite}; use embedded_io::asynch::{Read, Write}; +use static_cell::StaticCell; use {defmt_rtt as _, panic_probe as _}; -macro_rules! forever { +macro_rules! singleton { ($val:expr) => {{ type T = impl Sized; - static FOREVER: Forever = Forever::new(); - FOREVER.put_with(move || $val) + static STATIC_CELL: StaticCell = StaticCell::new(); + STATIC_CELL.init_with(move || $val) }}; } @@ -39,9 +38,11 @@ async fn net_task(stack: &'static Stack>) -> ! { } #[embassy_executor::main] -async fn main(spawner: Spawner, p: Peripherals) { +async fn main(spawner: Spawner) { info!("Hello World!"); + let p = embassy_rp::init(Default::default()); + // Include the WiFi firmware and Country Locale Matrix (CLM) blobs. let fw = include_bytes!("../../../firmware/43439A0.bin"); let clm = include_bytes!("../../../firmware/43439A0_clm.bin"); @@ -63,7 +64,7 @@ async fn main(spawner: Spawner, p: Peripherals) { let bus = MySpi { clk, dio }; let spi = ExclusiveDevice::new(bus, cs); - let state = forever!(cyw43::State::new()); + let state = singleton!(cyw43::State::new()); let (mut control, runner) = cyw43::new(state, pwr, spi, fw).await; spawner.spawn(wifi_task(runner)).unwrap(); @@ -84,10 +85,10 @@ async fn main(spawner: Spawner, p: Peripherals) { let seed = 0x0123_4567_89ab_cdef; // chosen by fair dice roll. guarenteed to be random. // Init network stack - let stack = &*forever!(Stack::new( + let stack = &*singleton!(Stack::new( net_device, config, - forever!(StackResources::<1, 2, 8>::new()), + singleton!(StackResources::<1, 2, 8>::new()), seed )); diff --git a/src/lib.rs b/src/lib.rs index 3932ce41..af2821e5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -17,8 +17,8 @@ use core::sync::atomic::Ordering; use core::task::Waker; use atomic_polyfill::AtomicBool; -use embassy_executor::time::{block_for, Duration, Timer}; use embassy_net::{PacketBoxExt, PacketBuf}; +use embassy_time::{block_for, Duration, Timer}; use embassy_util::blocking_mutex::raw::NoopRawMutex; use embassy_util::channel::mpmc::Channel; use embassy_util::yield_now; From 9218aff498aa4f9fca5d0aa3134fda6462801a2e Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Tue, 23 Aug 2022 01:06:14 +0200 Subject: [PATCH 025/178] Update Embassy. --- Cargo.toml | 3 ++- examples/rpi-pico-w/Cargo.toml | 20 ++++++-------------- src/lib.rs | 6 +++--- 3 files changed, 11 insertions(+), 18 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index c6120dac..cb6aa0b2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,7 +9,8 @@ log = ["dep:log"] [dependencies] embassy-time = { version = "0.1.0" } -embassy-util = { version = "0.1.0" } +embassy-sync = { version = "0.1.0" } +embassy-futures = { version = "0.1.0" } embassy-net = { version = "0.1.0" } atomic-polyfill = "0.1.5" diff --git a/examples/rpi-pico-w/Cargo.toml b/examples/rpi-pico-w/Cargo.toml index 98a3d105..53e72498 100644 --- a/examples/rpi-pico-w/Cargo.toml +++ b/examples/rpi-pico-w/Cargo.toml @@ -8,7 +8,6 @@ edition = "2021" cyw43 = { path = "../../", features = ["defmt"]} embassy-executor = { version = "0.1.0", features = ["defmt", "integrated-timers"] } embassy-time = { version = "0.1.0", features = ["defmt", "defmt-timestamp-uptime"] } -embassy-util = { version = "0.1.0" } embassy-rp = { version = "0.1.0", features = ["defmt", "unstable-traits", "nightly", "unstable-pac"] } embassy-net = { version = "0.1.0", features = ["defmt", "tcp", "dhcpv4", "medium-ethernet", "pool-16"] } atomic-polyfill = "0.1.5" @@ -29,19 +28,12 @@ heapless = "0.7.15" [patch.crates-io] -embassy-executor = { git = "https://github.com/embassy-rs/embassy", rev = "53fbd0efb3e77e1e3de948afde2b5bf1a5a9735f" } -embassy-time = { git = "https://github.com/embassy-rs/embassy", rev = "53fbd0efb3e77e1e3de948afde2b5bf1a5a9735f" } -embassy-util = { git = "https://github.com/embassy-rs/embassy", rev = "53fbd0efb3e77e1e3de948afde2b5bf1a5a9735f" } -embassy-rp = { git = "https://github.com/embassy-rs/embassy", rev = "53fbd0efb3e77e1e3de948afde2b5bf1a5a9735f" } -embassy-net = { git = "https://github.com/embassy-rs/embassy", rev = "53fbd0efb3e77e1e3de948afde2b5bf1a5a9735f" } -#embassy-executor = { path = "/home/dirbaio/embassy/embassy/embassy-executor" } -#embassy-util = { path = "/home/dirbaio/embassy/embassy/embassy-util" } -#embassy-rp = { path = "/home/dirbaio/embassy/embassy/embassy-rp" } -#embassy-net = { path = "/home/dirbaio/embassy/embassy/embassy-net" } -#smoltcp = { path = "./smoltcp" } - -#[patch."https://github.com/smoltcp-rs/smoltcp"] -#smoltcp = { path = "./smoltcp" } +embassy-executor = { git = "https://github.com/embassy-rs/embassy", rev = "cb9f0ef5b800ce4a22cde1805e0eb88425f1e07b" } +embassy-time = { git = "https://github.com/embassy-rs/embassy", rev = "cb9f0ef5b800ce4a22cde1805e0eb88425f1e07b" } +embassy-futures = { git = "https://github.com/embassy-rs/embassy", rev = "cb9f0ef5b800ce4a22cde1805e0eb88425f1e07b" } +embassy-sync = { git = "https://github.com/embassy-rs/embassy", rev = "cb9f0ef5b800ce4a22cde1805e0eb88425f1e07b" } +embassy-rp = { git = "https://github.com/embassy-rs/embassy", rev = "cb9f0ef5b800ce4a22cde1805e0eb88425f1e07b" } +embassy-net = { git = "https://github.com/embassy-rs/embassy", rev = "cb9f0ef5b800ce4a22cde1805e0eb88425f1e07b" } [profile.dev] debug = 2 diff --git a/src/lib.rs b/src/lib.rs index af2821e5..1c49771b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -17,11 +17,11 @@ use core::sync::atomic::Ordering; use core::task::Waker; use atomic_polyfill::AtomicBool; +use embassy_futures::yield_now; use embassy_net::{PacketBoxExt, PacketBuf}; +use embassy_sync::blocking_mutex::raw::NoopRawMutex; +use embassy_sync::channel::Channel; use embassy_time::{block_for, Duration, Timer}; -use embassy_util::blocking_mutex::raw::NoopRawMutex; -use embassy_util::channel::mpmc::Channel; -use embassy_util::yield_now; use embedded_hal_1::digital::blocking::OutputPin; use embedded_hal_async::spi::{SpiBusRead, SpiBusWrite, SpiDevice}; From bb76a29ff160580a934343a1ab10313e7b408da0 Mon Sep 17 00:00:00 2001 From: Daniel Bevenius Date: Wed, 24 Aug 2022 15:58:44 +0200 Subject: [PATCH 026/178] Add comment for AI constants This commit adds a comment about the AI_* constants. The motivation for using this definition is from looking in the following file: https://github.com/seemoo-lab/bcm-public/blob/master/firmware_patching/examples/ioctl/bcmdhd/include/aidmp.h#L2 https://github.com/seemoo-lab/bcm-public/blob/master/firmware_patching/examples/ioctl/bcmdhd/include/aidmp.h#L307-L361 --- src/lib.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index 1c49771b..b8c00329 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -91,6 +91,8 @@ const BACKPLANE_ADDRESS_MASK: u32 = 0x7FFF; const BACKPLANE_ADDRESS_32BIT_FLAG: u32 = 0x08000; const BACKPLANE_MAX_TRANSFER_SIZE: usize = 64; +// Broadcom AMBA (Advanced Microcontroller Bus Architecture) Interconnect (AI) +// constants const AI_IOCTRL_OFFSET: u32 = 0x408; const AI_IOCTRL_BIT_FGC: u8 = 0x0002; const AI_IOCTRL_BIT_CLOCK_EN: u8 = 0x0001; From 3826b4f7130366c92015f61566b4bb0783e0fee3 Mon Sep 17 00:00:00 2001 From: Daniel Bevenius Date: Thu, 25 Aug 2022 05:43:38 +0200 Subject: [PATCH 027/178] Rename REG_BUS_FEEDBEAD to REG_BUS_TEST_RO This commit renames the REG_BUS_FEEDBEAD to REG_BUS_TEST_RO (Read-Only) which is the name used in the specification, section 4.2.3 Table 6. It also adds a constant named REG_BUS_TEST_RW (Read-Write) to represent the dummy register which the host can use to write data and read back to check that the gSPI interface is working properly. --- src/lib.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 1c49771b..e287ca12 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -50,8 +50,8 @@ const REG_BUS_CTRL: u32 = 0x0; const REG_BUS_INTERRUPT: u32 = 0x04; // 16 bits - Interrupt status const REG_BUS_INTERRUPT_ENABLE: u32 = 0x06; // 16 bits - Interrupt mask const REG_BUS_STATUS: u32 = 0x8; -const REG_BUS_FEEDBEAD: u32 = 0x14; -const REG_BUS_TEST: u32 = 0x18; +const REG_BUS_TEST_RO: u32 = 0x14; +const REG_BUS_TEST_RW: u32 = 0x18; const REG_BUS_RESP_DELAY: u32 = 0x1c; // SPI_STATUS_REGISTER bits @@ -563,19 +563,19 @@ where Timer::after(Duration::from_millis(250)).await; info!("waiting for ping..."); - while self.read32_swapped(REG_BUS_FEEDBEAD).await != FEEDBEAD {} + while self.read32_swapped(REG_BUS_TEST_RO).await != FEEDBEAD {} info!("ping ok"); - self.write32_swapped(0x18, TEST_PATTERN).await; - let val = self.read32_swapped(REG_BUS_TEST).await; + self.write32_swapped(REG_BUS_TEST_RW, TEST_PATTERN).await; + let val = self.read32_swapped(REG_BUS_TEST_RW).await; assert_eq!(val, TEST_PATTERN); // 32bit, little endian. self.write32_swapped(REG_BUS_CTRL, 0x00010031).await; - let val = self.read32(FUNC_BUS, REG_BUS_FEEDBEAD).await; + let val = self.read32(FUNC_BUS, REG_BUS_TEST_RO).await; assert_eq!(val, FEEDBEAD); - let val = self.read32(FUNC_BUS, REG_BUS_TEST).await; + let val = self.read32(FUNC_BUS, REG_BUS_TEST_RW).await; assert_eq!(val, TEST_PATTERN); // No response delay in any of the funcs. From f2ac14b86f21c31221bba1e1653e2a9020d60d8e Mon Sep 17 00:00:00 2001 From: Daniel Bevenius Date: Wed, 24 Aug 2022 13:35:48 +0200 Subject: [PATCH 028/178] Add WORD_LENGTH_32/HIGH_SPEED constants MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This commit adds two constants which are intended to be used for setting the `Word Length` and `High Speed` fields in the gSPR register (address: 0x0000, bit: 0 and bit 4). Currently, this field is being set by the following line: ```rust // 32bit, little endian. self.write32_swapped(REG_BUS_CTRL, 0x00010031).await; ``` Assuming that we are sending these bits using the gSPI write protocol and using 16-bit word operation in little endian (which I think might be the default) then the data bytes should be packed like this: ``` +--+--+--+--+ |D1|D0|D3|D2| +--+--+--+--+ val (hex): 0x00010031 val (bin): 00000000000000010000000000110001 rotated(16): 00000000001100010000000000000001 ``` If we split val into bytes and rotated the bits we get: ``` Split into bytes: D3 D2 D1 D0 00000000 00000001 00000000 00110001 Rotate 16 and split into bytes: D1 D0 D3 D2 00000000 00110001 00000000 00000001 ``` Looking at the write procotol it seems to me that the above will indeed set the `Word Length` to 1 but will also set other values. ``` Status enable (1=default) D1 D0 D3 D2 ↓ 00000000 00110001 00000000 00000001 ↑↑ ↑↑ ↑ || |Word Length (1=32-bit) || | || Endianess (0=Little) || |High-speed mode (1=High speed (default)) | Interrupt polarity (1=high (default)) ``` This commit suggests adding the above mentioned constants for setting the only the word length field and the high speed field. --- src/lib.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 3932ce41..ef586c8f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -53,6 +53,8 @@ const REG_BUS_STATUS: u32 = 0x8; const REG_BUS_FEEDBEAD: u32 = 0x14; const REG_BUS_TEST: u32 = 0x18; const REG_BUS_RESP_DELAY: u32 = 0x1c; +const WORD_LENGTH_32: u32 = 0x1; +const HIGH_SPEED: u32 = 0x10; // SPI_STATUS_REGISTER bits const STATUS_DATA_NOT_AVAILABLE: u32 = 0x00000001; @@ -570,8 +572,8 @@ where let val = self.read32_swapped(REG_BUS_TEST).await; assert_eq!(val, TEST_PATTERN); - // 32bit, little endian. - self.write32_swapped(REG_BUS_CTRL, 0x00010031).await; + // 32-bit word length, little endian (which is the default endianess). + self.write32_swapped(REG_BUS_CTRL, WORD_LENGTH_32 | HIGH_SPEED).await; let val = self.read32(FUNC_BUS, REG_BUS_FEEDBEAD).await; assert_eq!(val, FEEDBEAD); From acaa8b3e8b80ccd49e04a6dc7d595d3a52d1ad0d Mon Sep 17 00:00:00 2001 From: Jan Niehusmann Date: Tue, 30 Aug 2022 20:36:57 +0000 Subject: [PATCH 029/178] Fix calculation of slice index total_len is already rounded up, so the `+ 3` is not needed. And even if it was, the calculation should have been `((total_len + 3) / 4)`. `(total_len + 3 / 4)` is equivalent to `total_len` and can overflow the slice, leading to a panic which can easily be triggered by sending large ICMP ECHO packets to the device. --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 8f439cf2..34170a26 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -762,7 +762,7 @@ where let bus = unsafe { &mut *bus }; async { bus.write(&[cmd]).await?; - bus.write(&buf[..(total_len + 3 / 4)]).await?; + bus.write(&buf[..(total_len / 4)]).await?; Ok(()) } }) From 95f3484b87d7ae829e14492a43d49a5a0a58f1b8 Mon Sep 17 00:00:00 2001 From: Jan Niehusmann Date: Tue, 6 Sep 2022 11:38:33 +0000 Subject: [PATCH 030/178] Implement minimal tx flow control The credit update code uses constants from https://github.com/Infineon/wifi-host-driver/blob/master/WiFi_Host_Driver/src/whd_sdpcm.c#L307-L317 --- src/lib.rs | 32 ++++++++++++++++++++++++++------ 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 34170a26..f818caf6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -525,6 +525,8 @@ pub struct Runner<'a, PWR, SPI> { ioctl_id: u16, sdpcm_seq: u8, backplane_window: u32, + + tx_seq_max: u8, } pub async fn new<'a, PWR, SPI>( @@ -546,6 +548,8 @@ where ioctl_id: 0, sdpcm_seq: 0, backplane_window: 0xAAAA_AAAA, + + tx_seq_max: 1, }; runner.init(firmware).await; @@ -670,13 +674,17 @@ where loop { // Send stuff // TODO flow control - if let IoctlState::Pending { kind, cmd, iface, buf } = self.state.ioctl_state.get() { - self.send_ioctl(kind, cmd, iface, unsafe { &*buf }).await; - self.state.ioctl_state.set(IoctlState::Sent { buf }); - } + if self.sdpcm_seq == self.tx_seq_max || self.tx_seq_max.wrapping_sub(self.sdpcm_seq) & 0x80 != 0 { + warn!("TX stalled"); + } else { + if let IoctlState::Pending { kind, cmd, iface, buf } = self.state.ioctl_state.get() { + self.send_ioctl(kind, cmd, iface, unsafe { &*buf }).await; + self.state.ioctl_state.set(IoctlState::Sent { buf }); + } - if let Ok(p) = self.state.tx_channel.try_recv() { - self.send_packet(&p).await; + if let Ok(p) = self.state.tx_channel.try_recv() { + self.send_packet(&p).await; + } } // Receive stuff @@ -788,6 +796,8 @@ where return; } + self.update_credit(&sdpcm_header); + let channel = sdpcm_header.channel_and_flags & 0x0f; let payload = &packet[sdpcm_header.header_length as _..]; @@ -894,6 +904,16 @@ where } } + fn update_credit(&mut self, sdpcm_header: &SdpcmHeader) { + if sdpcm_header.channel_and_flags & 0xf < 3 { + let mut tx_seq_max = sdpcm_header.bus_data_credit; + if tx_seq_max - self.sdpcm_seq > 0x40 { + tx_seq_max = self.sdpcm_seq + 2; + } + self.tx_seq_max = tx_seq_max; + } + } + async fn send_ioctl(&mut self, kind: u32, cmd: u32, iface: u32, data: &[u8]) { let mut buf = [0; 512]; let buf8 = slice8_mut(&mut buf); From 5c4d6232ae5822d70cba4dbd60cfe348a7f0d687 Mon Sep 17 00:00:00 2001 From: Jan Niehusmann Date: Tue, 6 Sep 2022 20:50:27 +0000 Subject: [PATCH 031/178] Fixes after review - rename tx_seq_max to sdpcm_seq_max - make sure we have credit for each packet we send --- src/lib.rs | 29 ++++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index f818caf6..5e79e6e4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -526,7 +526,7 @@ pub struct Runner<'a, PWR, SPI> { sdpcm_seq: u8, backplane_window: u32, - tx_seq_max: u8, + sdpcm_seq_max: u8, } pub async fn new<'a, PWR, SPI>( @@ -549,7 +549,7 @@ where sdpcm_seq: 0, backplane_window: 0xAAAA_AAAA, - tx_seq_max: 1, + sdpcm_seq_max: 1, }; runner.init(firmware).await; @@ -673,17 +673,20 @@ where let mut buf = [0; 512]; loop { // Send stuff - // TODO flow control - if self.sdpcm_seq == self.tx_seq_max || self.tx_seq_max.wrapping_sub(self.sdpcm_seq) & 0x80 != 0 { + // TODO flow control not yet complete + if !self.has_credit() { warn!("TX stalled"); } else { if let IoctlState::Pending { kind, cmd, iface, buf } = self.state.ioctl_state.get() { self.send_ioctl(kind, cmd, iface, unsafe { &*buf }).await; self.state.ioctl_state.set(IoctlState::Sent { buf }); } - - if let Ok(p) = self.state.tx_channel.try_recv() { - self.send_packet(&p).await; + if !self.has_credit() { + warn!("TX stalled"); + } else { + if let Ok(p) = self.state.tx_channel.try_recv() { + self.send_packet(&p).await; + } } } @@ -906,14 +909,18 @@ where fn update_credit(&mut self, sdpcm_header: &SdpcmHeader) { if sdpcm_header.channel_and_flags & 0xf < 3 { - let mut tx_seq_max = sdpcm_header.bus_data_credit; - if tx_seq_max - self.sdpcm_seq > 0x40 { - tx_seq_max = self.sdpcm_seq + 2; + let mut sdpcm_seq_max = sdpcm_header.bus_data_credit; + if sdpcm_seq_max - self.sdpcm_seq > 0x40 { + sdpcm_seq_max = self.sdpcm_seq + 2; } - self.tx_seq_max = tx_seq_max; + self.sdpcm_seq_max = sdpcm_seq_max; } } + fn has_credit(&mut self) -> bool { + self.sdpcm_seq != self.sdpcm_seq_max && self.sdpcm_seq_max.wrapping_sub(self.sdpcm_seq) & 0x80 == 0 + } + async fn send_ioctl(&mut self, kind: u32, cmd: u32, iface: u32, data: &[u8]) { let mut buf = [0; 512]; let buf8 = slice8_mut(&mut buf); From ea0738c4851cbeb87de0d40ce1e8246368db4c6b Mon Sep 17 00:00:00 2001 From: Jan Niehusmann Date: Tue, 6 Sep 2022 21:06:47 +0000 Subject: [PATCH 032/178] Add gpio_set Example: Blink LED ``` loop { info!("on"); control.gpio_set(0, true).await; Timer::after(Duration::from_millis(200)).await; info!("off"); control.gpio_set(0, false).await; Timer::after(Duration::from_millis(200)).await; } ``` --- src/lib.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index 5e79e6e4..21b8b2d8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -397,6 +397,12 @@ impl<'a> Control<'a> { info!("JOINED"); } + pub async fn gpio_set(&mut self, gpio_n: u8, gpio_en: bool) { + assert!(gpio_n < 3); + self.set_iovar_u32x2("gpioout", 1 << gpio_n, if gpio_en { 1 << gpio_n } else { 0 }) + .await + } + async fn set_iovar_u32x2(&mut self, name: &str, val1: u32, val2: u32) { let mut buf = [0; 8]; buf[0..4].copy_from_slice(&val1.to_le_bytes()); From fe5229670f40757f63e56c68388be241b5470bf6 Mon Sep 17 00:00:00 2001 From: Daniel Bevenius Date: Fri, 9 Sep 2022 11:57:02 +0200 Subject: [PATCH 033/178] Add constants for ioctl commands This commit adds contants for the IOCTL commands that are currently used in cyw43::Control. --- src/lib.rs | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 21b8b2d8..3f801fe9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -126,6 +126,12 @@ const IRQ_F1_INTR: u16 = 0x2000; const IRQ_F2_INTR: u16 = 0x4000; const IRQ_F3_INTR: u16 = 0x8000; +const IOCTL_CMD_UP: u32 = 2; +const IOCTL_CMD_SET_SSID: u32 = 26; +const IOCTL_CMD_SET_VAR: u32 = 263; +const IOCTL_CMD_GET_VAR: u32 = 262; +const IOCTL_CMD_SET_PASSPHRASE: u32 = 268; + #[derive(Clone, Copy, PartialEq, Eq)] enum Core { WLAN = 0, @@ -263,7 +269,8 @@ impl<'a> Control<'a> { buf[0..8].copy_from_slice(b"clmload\x00"); buf[8..20].copy_from_slice(&header.to_bytes()); buf[20..][..chunk.len()].copy_from_slice(&chunk); - self.ioctl(2, 263, 0, &mut buf[..8 + 12 + chunk.len()]).await; + self.ioctl(2, IOCTL_CMD_SET_VAR, 0, &mut buf[..8 + 12 + chunk.len()]) + .await; } // check clmload ok @@ -323,7 +330,7 @@ impl<'a> Control<'a> { Timer::after(Duration::from_millis(100)).await; // set wifi up - self.ioctl(2, 2, 0, &mut []).await; + self.ioctl(2, IOCTL_CMD_UP, 0, &mut []).await; Timer::after(Duration::from_millis(100)).await; @@ -360,7 +367,7 @@ impl<'a> Control<'a> { ssid: [0; 32], }; i.ssid[..ssid.len()].copy_from_slice(ssid.as_bytes()); - self.ioctl(2, 26, 0, &mut i.to_bytes()).await; // set_ssid + self.ioctl(2, IOCTL_CMD_SET_SSID, 0, &mut i.to_bytes()).await; // set_ssid info!("JOINED"); } @@ -381,7 +388,7 @@ impl<'a> Control<'a> { passphrase: [0; 64], }; pfi.passphrase[..passphrase.len()].copy_from_slice(passphrase.as_bytes()); - self.ioctl(2, 268, 0, &mut pfi.to_bytes()).await; // WLC_SET_WSEC_PMK + self.ioctl(2, IOCTL_CMD_SET_PASSPHRASE, 0, &mut pfi.to_bytes()).await; // WLC_SET_WSEC_PMK self.ioctl_set_u32(20, 0, 1).await; // set_infra = 1 self.ioctl_set_u32(22, 0, 0).await; // set_auth = 0 (open) @@ -430,7 +437,7 @@ impl<'a> Control<'a> { buf[name.len() + 1..][..val.len()].copy_from_slice(val); let total_len = name.len() + 1 + val.len(); - self.ioctl(2, 263, 0, &mut buf[..total_len]).await; + self.ioctl(2, IOCTL_CMD_SET_VAR, 0, &mut buf[..total_len]).await; } // TODO this is not really working, it always returns all zeros. @@ -442,7 +449,7 @@ impl<'a> Control<'a> { buf[name.len()] = 0; let total_len = max(name.len() + 1, res.len()); - let res_len = self.ioctl(0, 262, 0, &mut buf[..total_len]).await; + let res_len = self.ioctl(0, IOCTL_CMD_GET_VAR, 0, &mut buf[..total_len]).await; let out_len = min(res.len(), res_len); res[..out_len].copy_from_slice(&buf[..out_len]); From f0b7f43c4104ea15c860d557c4a507681cba0d0d Mon Sep 17 00:00:00 2001 From: Daniel Bevenius Date: Fri, 9 Sep 2022 14:15:19 +0200 Subject: [PATCH 034/178] Use wrapping_sub in update_credit MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This commit uses wrapping_sub for subtraction in update_credit. The motivation for this is that currently the rpi-pico-w example panics (at least for me) with the following error: 3.825277 INFO init done └─ cyw43::{impl#4}::init::{async_fn#0} @ /embassy/cyw43/src/fmt.rs:138 3.825486 INFO Downloading CLM... └─ cyw43::{impl#2}::init::{async_fn#0} @ /embassy/cyw43/src/fmt.rs:138 3.841328 WARN TX stalled └─ cyw43::{impl#4}::run::{async_fn#0} @ /embassy/cyw43/src/fmt.rs:151 3.845549 ERROR panicked at 'attempt to subtract with overflow', /embassy/cyw43/src/lib.rs:919:16 └─ panic_probe::print_defmt::print @ .cargo/registry/src/github.com-1ecc6299db9ec823/panic-probe-0.3.0/src/lib.rs:91 ──────────────────────────────────────────────────────────────────────────────── stack backtrace: 0: HardFaultTrampoline 1: lib::inline::__udf at ./asm/inline.rs:181:5 2: __udf at ./asm/lib.rs:51:17 3: cortex_m::asm::udf at .cargo/registry/src/github.com-1ecc6299db9ec823/cortex-m-0.7.6/src/asm.rs:43:5 4: rust_begin_unwind at .cargo/registry/src/github.com-1ecc6299db9ec823/panic-probe-0.3.0/src/lib.rs:72:9 5: core::panicking::panic_fmt at rustc/1c7b36d4db582cb47513a6c7176baaec1c3346ab/library/core/src/panicking.rs:142:14 6: core::panicking::panic at /rustc/1c7b36d4db582cb47513a6c7176baaec1c3346ab/library/core/src/panicking.rs:48:5 7: cyw43::Runner::update_credit at /embassy/cyw43/src/lib.rs:919:16 8: cyw43::Runner::rx at /embassy/cyw43/src/lib.rs:808:9 9: cyw43::Runner::run::{{closure}} at /embassy/cyw43/src/lib.rs:727:21 10: as core::future::future::Future>::poll at /rustc/1c7b36d4db582cb47513a6c7176baaec1c3346ab/library/core/src/future/mod.rs:91:19 11: cyw43_example_rpi_pico_w::__wifi_task_task::{{closure}} at src/main.rs:32:17 --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 21b8b2d8..d9a21f4b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -916,7 +916,7 @@ where fn update_credit(&mut self, sdpcm_header: &SdpcmHeader) { if sdpcm_header.channel_and_flags & 0xf < 3 { let mut sdpcm_seq_max = sdpcm_header.bus_data_credit; - if sdpcm_seq_max - self.sdpcm_seq > 0x40 { + if sdpcm_seq_max.wrapping_sub(self.sdpcm_seq) > 0x40 { sdpcm_seq_max = self.sdpcm_seq + 2; } self.sdpcm_seq_max = sdpcm_seq_max; From be20512f17210ae179078c4bb082211d00d828da Mon Sep 17 00:00:00 2001 From: Daniel Bevenius Date: Mon, 12 Sep 2022 11:44:21 +0200 Subject: [PATCH 035/178] Add contants and update comment about ALP This commit add two constants and updates the comment about ALP. It was not easy to find the definition of ALP but after searching I found what I believe is the correct definition in section 3.3 "Clocks" in the referenced document below. Active Low Power (ALP): Supplied by an internal or external oscillator. This clock is requested by cores when accessing backplane registers in other cores or when performing minor computations. When an external crystal is used to provide reference clock, ALP clock frequency is determined by the frequency of the external oscillator. A 37.4 MHz reference clock is recommended. Refs: https://www.infineon.com/dgdl/Infineon-AN214828_Power_Consumption_Measurements-ApplicationNotes-v03_00-EN.pdf?fileId=8ac78c8c7cdc391c017d0d2803a4630d --- src/lib.rs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index e145b821..8e43f51f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -92,6 +92,9 @@ const BACKPLANE_WINDOW_SIZE: usize = 0x8000; const BACKPLANE_ADDRESS_MASK: u32 = 0x7FFF; const BACKPLANE_ADDRESS_32BIT_FLAG: u32 = 0x08000; const BACKPLANE_MAX_TRANSFER_SIZE: usize = 64; +// Active Low Power (ALP) clock constants +const BACKPLANE_ALP_AVAIL_REQ: u8 = 0x08; +const BACKPLANE_ALP_AVAIL: u8 = 0x40; // Broadcom AMBA (Advanced Microcontroller Bus Architecture) Interconnect (AI) // constants @@ -603,10 +606,11 @@ where // seems to break backplane??? eat the 4-byte delay instead, that's what the vendor drivers do... //self.write32(FUNC_BUS, REG_BUS_RESP_DELAY, 0).await; - // Init ALP (no idea what that stands for) clock - self.write8(FUNC_BACKPLANE, REG_BACKPLANE_CHIP_CLOCK_CSR, 0x08).await; + // Init ALP (Active Low Power) clock + self.write8(FUNC_BACKPLANE, REG_BACKPLANE_CHIP_CLOCK_CSR, BACKPLANE_ALP_AVAIL_REQ) + .await; info!("waiting for clock..."); - while self.read8(FUNC_BACKPLANE, REG_BACKPLANE_CHIP_CLOCK_CSR).await & 0x40 == 0 {} + while self.read8(FUNC_BACKPLANE, REG_BACKPLANE_CHIP_CLOCK_CSR).await & BACKPLANE_ALP_AVAIL == 0 {} info!("clock ok"); let chip_id = self.bp_read16(0x1800_0000).await; From 96214f9db658be6d84082c8ddac21dcf4b09c3ff Mon Sep 17 00:00:00 2001 From: Daniel Bevenius Date: Thu, 15 Sep 2022 09:56:12 +0200 Subject: [PATCH 036/178] Add constants for channel types --- src/lib.rs | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index e145b821..def738b8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -132,6 +132,10 @@ const IOCTL_CMD_SET_VAR: u32 = 263; const IOCTL_CMD_GET_VAR: u32 = 262; const IOCTL_CMD_SET_PASSPHRASE: u32 = 268; +const CHANNEL_TYPE_CONTROL: u8 = 0; +const CHANNEL_TYPE_EVENT: u8 = 1; +const CHANNEL_TYPE_DATA: u8 = 2; + #[derive(Clone, Copy, PartialEq, Eq)] enum Core { WLAN = 0, @@ -755,7 +759,7 @@ where len: total_len as u16, // TODO does this len need to be rounded up to u32? len_inv: !total_len as u16, sequence: seq, - channel_and_flags: 2, // data channel + channel_and_flags: CHANNEL_TYPE_DATA, next_length: 0, header_length: SdpcmHeader::SIZE as _, wireless_flow_control: 0, @@ -819,7 +823,7 @@ where let payload = &packet[sdpcm_header.header_length as _..]; match channel { - 0 => { + CHANNEL_TYPE_CONTROL => { if payload.len() < CdcHeader::SIZE { warn!("payload too short, len={}", payload.len()); return; @@ -840,7 +844,7 @@ where } } } - 1 => { + CHANNEL_TYPE_EVENT => { let bcd_header = BcdHeader::from_bytes(&payload[..BcdHeader::SIZE].try_into().unwrap()); trace!(" {:?}", bcd_header); @@ -897,7 +901,7 @@ where evt_data ); } - 2 => { + CHANNEL_TYPE_DATA => { let bcd_header = BcdHeader::from_bytes(&payload[..BcdHeader::SIZE].try_into().unwrap()); trace!(" {:?}", bcd_header); @@ -948,7 +952,7 @@ where len: total_len as u16, // TODO does this len need to be rounded up to u32? len_inv: !total_len as u16, sequence: sdpcm_seq, - channel_and_flags: 0, // control channel + channel_and_flags: CHANNEL_TYPE_CONTROL, next_length: 0, header_length: SdpcmHeader::SIZE as _, wireless_flow_control: 0, From 520860622b5d42471c58a041ae37337cb92e3fc9 Mon Sep 17 00:00:00 2001 From: Daniel Bevenius Date: Sat, 17 Sep 2022 09:06:23 +0200 Subject: [PATCH 037/178] Make self parameter to has_credit non-mutable --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 4baaaa51..d09be506 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -938,7 +938,7 @@ where } } - fn has_credit(&mut self) -> bool { + fn has_credit(&self) -> bool { self.sdpcm_seq != self.sdpcm_seq_max && self.sdpcm_seq_max.wrapping_sub(self.sdpcm_seq) & 0x80 == 0 } From 483edf694b399779636b6bfd8cf243eb430d1323 Mon Sep 17 00:00:00 2001 From: Daniel Bevenius Date: Sun, 11 Sep 2022 09:34:39 +0200 Subject: [PATCH 038/178] Introduce IoctlType enum for IOCTL types This commit introduces an enum to represent the IOCTL command types available, the direction of the data transfer (Get and Set). --- src/lib.rs | 33 ++++++++++++++++++++++----------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index d09be506..a6b26188 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -139,6 +139,12 @@ const CHANNEL_TYPE_CONTROL: u8 = 0; const CHANNEL_TYPE_EVENT: u8 = 1; const CHANNEL_TYPE_DATA: u8 = 2; +#[derive(Clone, Copy)] +pub enum IoctlType { + Get = 0, + Set = 2, +} + #[derive(Clone, Copy, PartialEq, Eq)] enum Core { WLAN = 0, @@ -212,7 +218,7 @@ enum IoctlState { Idle, Pending { - kind: u32, + kind: IoctlType, cmd: u32, iface: u32, buf: *mut [u8], @@ -276,7 +282,7 @@ impl<'a> Control<'a> { buf[0..8].copy_from_slice(b"clmload\x00"); buf[8..20].copy_from_slice(&header.to_bytes()); buf[20..][..chunk.len()].copy_from_slice(&chunk); - self.ioctl(2, IOCTL_CMD_SET_VAR, 0, &mut buf[..8 + 12 + chunk.len()]) + self.ioctl(IoctlType::Set, IOCTL_CMD_SET_VAR, 0, &mut buf[..8 + 12 + chunk.len()]) .await; } @@ -337,7 +343,7 @@ impl<'a> Control<'a> { Timer::after(Duration::from_millis(100)).await; // set wifi up - self.ioctl(2, IOCTL_CMD_UP, 0, &mut []).await; + self.ioctl(IoctlType::Set, IOCTL_CMD_UP, 0, &mut []).await; Timer::after(Duration::from_millis(100)).await; @@ -374,7 +380,8 @@ impl<'a> Control<'a> { ssid: [0; 32], }; i.ssid[..ssid.len()].copy_from_slice(ssid.as_bytes()); - self.ioctl(2, IOCTL_CMD_SET_SSID, 0, &mut i.to_bytes()).await; // set_ssid + self.ioctl(IoctlType::Set, IOCTL_CMD_SET_SSID, 0, &mut i.to_bytes()) + .await; // set_ssid info!("JOINED"); } @@ -395,7 +402,8 @@ impl<'a> Control<'a> { passphrase: [0; 64], }; pfi.passphrase[..passphrase.len()].copy_from_slice(passphrase.as_bytes()); - self.ioctl(2, IOCTL_CMD_SET_PASSPHRASE, 0, &mut pfi.to_bytes()).await; // WLC_SET_WSEC_PMK + self.ioctl(IoctlType::Set, IOCTL_CMD_SET_PASSPHRASE, 0, &mut pfi.to_bytes()) + .await; // WLC_SET_WSEC_PMK self.ioctl_set_u32(20, 0, 1).await; // set_infra = 1 self.ioctl_set_u32(22, 0, 0).await; // set_auth = 0 (open) @@ -406,7 +414,7 @@ impl<'a> Control<'a> { ssid: [0; 32], }; i.ssid[..ssid.len()].copy_from_slice(ssid.as_bytes()); - self.ioctl(2, 26, 0, &mut i.to_bytes()).await; // set_ssid + self.ioctl(IoctlType::Set, 26, 0, &mut i.to_bytes()).await; // set_ssid info!("JOINED"); } @@ -444,7 +452,8 @@ impl<'a> Control<'a> { buf[name.len() + 1..][..val.len()].copy_from_slice(val); let total_len = name.len() + 1 + val.len(); - self.ioctl(2, IOCTL_CMD_SET_VAR, 0, &mut buf[..total_len]).await; + self.ioctl(IoctlType::Set, IOCTL_CMD_SET_VAR, 0, &mut buf[..total_len]) + .await; } // TODO this is not really working, it always returns all zeros. @@ -456,7 +465,9 @@ impl<'a> Control<'a> { buf[name.len()] = 0; let total_len = max(name.len() + 1, res.len()); - let res_len = self.ioctl(0, IOCTL_CMD_GET_VAR, 0, &mut buf[..total_len]).await; + let res_len = self + .ioctl(IoctlType::Get, IOCTL_CMD_GET_VAR, 0, &mut buf[..total_len]) + .await; let out_len = min(res.len(), res_len); res[..out_len].copy_from_slice(&buf[..out_len]); @@ -465,10 +476,10 @@ impl<'a> Control<'a> { async fn ioctl_set_u32(&mut self, cmd: u32, iface: u32, val: u32) { let mut buf = val.to_le_bytes(); - self.ioctl(2, cmd, 0, &mut buf).await; + self.ioctl(IoctlType::Set, cmd, 0, &mut buf).await; } - async fn ioctl(&mut self, kind: u32, cmd: u32, iface: u32, buf: &mut [u8]) -> usize { + async fn ioctl(&mut self, kind: IoctlType, cmd: u32, iface: u32, buf: &mut [u8]) -> usize { // TODO cancel ioctl on future drop. while !matches!(self.state.ioctl_state.get(), IoctlState::Idle) { @@ -942,7 +953,7 @@ where self.sdpcm_seq != self.sdpcm_seq_max && self.sdpcm_seq_max.wrapping_sub(self.sdpcm_seq) & 0x80 == 0 } - async fn send_ioctl(&mut self, kind: u32, cmd: u32, iface: u32, data: &[u8]) { + async fn send_ioctl(&mut self, kind: IoctlType, cmd: u32, iface: u32, data: &[u8]) { let mut buf = [0; 512]; let buf8 = slice8_mut(&mut buf); From 8f21a5b11698e0c84e61e22c9c505d59d5e50f67 Mon Sep 17 00:00:00 2001 From: Daniel Bevenius Date: Fri, 23 Sep 2022 08:27:18 +0200 Subject: [PATCH 039/178] Add comment about bus:txglom iovar This commit adds a comment to the setting of the iovar `bus:txglom`. The motivation for this is that I had not heard of 'glom/glomming' before and having a comment might help others that are not familar with the term. --- src/lib.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index a6b26188..7a09a539 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -291,6 +291,9 @@ impl<'a> Control<'a> { info!("Configuring misc stuff..."); + // Disable tx gloming which transfers multiple packets in one request. + // 'glom' is short for "conglomerate" which means "gather together into + // a compact mass". self.set_iovar_u32("bus:txglom", 0).await; self.set_iovar_u32("apsta", 1).await; From 3ba0b3ef3b62ed05afed5ddeec8afaedc87e190e Mon Sep 17 00:00:00 2001 From: Daniel Bevenius Date: Fri, 23 Sep 2022 09:04:59 +0200 Subject: [PATCH 040/178] Comment out extra Timer:after calls This commit comments out two Timer::after calls which look like they go together with previous instructions, but those instructions are currently commented out, so it looks like these calls are not currently needed. --- src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index a6b26188..013b2343 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -315,14 +315,14 @@ impl<'a> Control<'a> { self.set_iovar_u32("bus:txglom", 0).await; Timer::after(Duration::from_millis(100)).await; //self.set_iovar_u32("apsta", 1).await; // this crashes, also we already did it before...?? - Timer::after(Duration::from_millis(100)).await; + //Timer::after(Duration::from_millis(100)).await; self.set_iovar_u32("ampdu_ba_wsize", 8).await; Timer::after(Duration::from_millis(100)).await; self.set_iovar_u32("ampdu_mpdu", 4).await; Timer::after(Duration::from_millis(100)).await; //self.set_iovar_u32("ampdu_rx_factor", 0).await; // this crashes - Timer::after(Duration::from_millis(100)).await; + //Timer::after(Duration::from_millis(100)).await; // evts let mut evts = EventMask { From 28bf4b7b6da0e0f6ce0878580363f135c7e7a879 Mon Sep 17 00:00:00 2001 From: Daniel Bevenius Date: Fri, 23 Sep 2022 09:35:54 +0200 Subject: [PATCH 041/178] Add const for IOCTL ANTDIV --- src/lib.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index a6b26188..f543f75e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -131,6 +131,7 @@ const IRQ_F3_INTR: u16 = 0x8000; const IOCTL_CMD_UP: u32 = 2; const IOCTL_CMD_SET_SSID: u32 = 26; +const IOCTL_CMD_ANTDIV: u32 = 64; const IOCTL_CMD_SET_VAR: u32 = 263; const IOCTL_CMD_GET_VAR: u32 = 262; const IOCTL_CMD_SET_PASSPHRASE: u32 = 268; @@ -310,7 +311,8 @@ impl<'a> Control<'a> { // set country takes some time, next ioctls fail if we don't wait. Timer::after(Duration::from_millis(100)).await; - self.ioctl_set_u32(64, 0, 0).await; // WLC_SET_ANTDIV + // Set antenna to chip antenna + self.ioctl_set_u32(IOCTL_CMD_ANTDIV, 0, 0).await; self.set_iovar_u32("bus:txglom", 0).await; Timer::after(Duration::from_millis(100)).await; From 281cbcb1e8f2df2af6584c8dcef2d45b1ef73f4b Mon Sep 17 00:00:00 2001 From: Daniel Bevenius Date: Fri, 23 Sep 2022 09:39:29 +0200 Subject: [PATCH 042/178] Update ioctl_set_u32 to pass through iface param This commit updates ioctl_set_u32 to pass through the `iface` parameter to self.iotcl. --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index a6b26188..80e07662 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -476,7 +476,7 @@ impl<'a> Control<'a> { async fn ioctl_set_u32(&mut self, cmd: u32, iface: u32, val: u32) { let mut buf = val.to_le_bytes(); - self.ioctl(IoctlType::Set, cmd, 0, &mut buf).await; + self.ioctl(IoctlType::Set, cmd, iface, &mut buf).await; } async fn ioctl(&mut self, kind: IoctlType, cmd: u32, iface: u32, buf: &mut [u8]) -> usize { From 9aaefa6e7163c80b26546a8ce58740b437a5a03e Mon Sep 17 00:00:00 2001 From: Daniel Bevenius Date: Fri, 23 Sep 2022 15:01:09 +0200 Subject: [PATCH 043/178] Add constants for cmd_word arguments This commit adds constants intended to be used with the `cmd_word` function. The motivation for this to (hopefully) improve the readability of the code. --- src/lib.rs | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index a6b26188..c72f29ed 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -32,6 +32,13 @@ fn swap16(x: u32) -> u32 { x.rotate_left(16) } +// CYW_SPID command structure constants. +const WRITE: bool = true; +const READ: bool = false; +const INC_ADDR: bool = true; +#[allow(unused)] +const FIXED_ADDR: bool = false; + fn cmd_word(write: bool, incr: bool, func: u32, addr: u32, len: u32) -> u32 { (write as u32) << 31 | (incr as u32) << 30 | (func & 0b11) << 28 | (addr & 0x1FFFF) << 11 | (len & 0x7FF) } @@ -734,7 +741,7 @@ where if status & STATUS_F2_PKT_AVAILABLE != 0 { let len = (status & STATUS_F2_PKT_LEN_MASK) >> STATUS_F2_PKT_LEN_SHIFT; - let cmd = cmd_word(false, true, FUNC_WLAN, 0, len); + let cmd = cmd_word(READ, INC_ADDR, FUNC_WLAN, 0, len); self.spi .transaction(|bus| { @@ -799,7 +806,7 @@ where trace!(" {:02x}", &buf8[..total_len.min(48)]); - let cmd = cmd_word(true, true, FUNC_WLAN, 0, total_len as _); + let cmd = cmd_word(WRITE, INC_ADDR, FUNC_WLAN, 0, total_len as _); self.spi .transaction(|bus| { let bus = unsafe { &mut *bus }; @@ -993,7 +1000,7 @@ where trace!(" {:02x}", &buf8[..total_len.min(48)]); - let cmd = cmd_word(true, true, FUNC_WLAN, 0, total_len as _); + let cmd = cmd_word(WRITE, INC_ADDR, FUNC_WLAN, 0, total_len as _); self.spi .transaction(|bus| { @@ -1081,7 +1088,7 @@ where self.backplane_set_window(addr).await; - let cmd = cmd_word(false, true, FUNC_BACKPLANE, window_offs, len as u32); + let cmd = cmd_word(READ, INC_ADDR, FUNC_BACKPLANE, window_offs, len as u32); self.spi .transaction(|bus| { @@ -1126,7 +1133,7 @@ where self.backplane_set_window(addr).await; - let cmd = cmd_word(true, true, FUNC_BACKPLANE, window_offs, len as u32); + let cmd = cmd_word(WRITE, INC_ADDR, FUNC_BACKPLANE, window_offs, len as u32); self.spi .transaction(|bus| { @@ -1245,7 +1252,7 @@ where } async fn readn(&mut self, func: u32, addr: u32, len: u32) -> u32 { - let cmd = cmd_word(false, true, func, addr, len); + let cmd = cmd_word(READ, INC_ADDR, func, addr, len); let mut buf = [0; 1]; self.spi @@ -1268,7 +1275,7 @@ where } async fn writen(&mut self, func: u32, addr: u32, val: u32, len: u32) { - let cmd = cmd_word(true, true, func, addr, len); + let cmd = cmd_word(WRITE, INC_ADDR, func, addr, len); self.spi .transaction(|bus| { @@ -1283,7 +1290,7 @@ where } async fn read32_swapped(&mut self, addr: u32) -> u32 { - let cmd = cmd_word(false, true, FUNC_BUS, addr, 4); + let cmd = cmd_word(READ, INC_ADDR, FUNC_BUS, addr, 4); let mut buf = [0; 1]; self.spi @@ -1302,7 +1309,7 @@ where } async fn write32_swapped(&mut self, addr: u32, val: u32) { - let cmd = cmd_word(true, true, FUNC_BUS, addr, 4); + let cmd = cmd_word(WRITE, INC_ADDR, FUNC_BUS, addr, 4); self.spi .transaction(|bus| { From 9962db4ecf227792d777ff0bc91d9e4d50d24f85 Mon Sep 17 00:00:00 2001 From: Daniel Bevenius Date: Fri, 23 Sep 2022 13:29:33 +0200 Subject: [PATCH 044/178] Suppress compiler warnings This commit adds the allow(unused) attribute to functions and constants that are not currently used. There is one warning remaining but https://github.com/embassy-rs/cyw43/pull/23 attempts to address that one. The constants have been moved into a module to allow the attribute to be applied to the module as a whole. The motivation for this is that it will hopefully make it easier to spot new warnings that might be introduced by new, or updated code. --- src/lib.rs | 198 ++++++++++++++++++++++++++----------------------- src/structs.rs | 3 + 2 files changed, 107 insertions(+), 94 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index c7e0285f..b8ce2e2a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -32,13 +32,6 @@ fn swap16(x: u32) -> u32 { x.rotate_left(16) } -// CYW_SPID command structure constants. -const WRITE: bool = true; -const READ: bool = false; -const INC_ADDR: bool = true; -#[allow(unused)] -const FIXED_ADDR: bool = false; - fn cmd_word(write: bool, incr: bool, func: u32, addr: u32, len: u32) -> u32 { (write as u32) << 31 | (incr as u32) << 30 | (func & 0b11) << 28 | (addr & 0x1FFFF) << 11 | (len & 0x7FF) } @@ -48,104 +41,114 @@ fn slice8_mut(x: &mut [u32]) -> &mut [u8] { unsafe { slice::from_raw_parts_mut(x.as_mut_ptr() as _, len) } } -const FUNC_BUS: u32 = 0; -const FUNC_BACKPLANE: u32 = 1; -const FUNC_WLAN: u32 = 2; -const FUNC_BT: u32 = 3; +mod constants { + #![allow(unused)] + pub(crate) const FUNC_BUS: u32 = 0; + pub(crate) const FUNC_BACKPLANE: u32 = 1; + pub(crate) const FUNC_WLAN: u32 = 2; + pub(crate) const FUNC_BT: u32 = 3; -const REG_BUS_CTRL: u32 = 0x0; -const REG_BUS_INTERRUPT: u32 = 0x04; // 16 bits - Interrupt status -const REG_BUS_INTERRUPT_ENABLE: u32 = 0x06; // 16 bits - Interrupt mask -const REG_BUS_STATUS: u32 = 0x8; -const REG_BUS_TEST_RO: u32 = 0x14; -const REG_BUS_TEST_RW: u32 = 0x18; -const REG_BUS_RESP_DELAY: u32 = 0x1c; -const WORD_LENGTH_32: u32 = 0x1; -const HIGH_SPEED: u32 = 0x10; + pub(crate) const REG_BUS_CTRL: u32 = 0x0; + pub(crate) const REG_BUS_INTERRUPT: u32 = 0x04; // 16 bits - Interrupt status + pub(crate) const REG_BUS_INTERRUPT_ENABLE: u32 = 0x06; // 16 bits - Interrupt mask + pub(crate) const REG_BUS_STATUS: u32 = 0x8; + pub(crate) const REG_BUS_TEST_RO: u32 = 0x14; + pub(crate) const REG_BUS_TEST_RW: u32 = 0x18; + pub(crate) const REG_BUS_RESP_DELAY: u32 = 0x1c; + pub(crate) const WORD_LENGTH_32: u32 = 0x1; + pub(crate) const HIGH_SPEED: u32 = 0x10; -// SPI_STATUS_REGISTER bits -const STATUS_DATA_NOT_AVAILABLE: u32 = 0x00000001; -const STATUS_UNDERFLOW: u32 = 0x00000002; -const STATUS_OVERFLOW: u32 = 0x00000004; -const STATUS_F2_INTR: u32 = 0x00000008; -const STATUS_F3_INTR: u32 = 0x00000010; -const STATUS_F2_RX_READY: u32 = 0x00000020; -const STATUS_F3_RX_READY: u32 = 0x00000040; -const STATUS_HOST_CMD_DATA_ERR: u32 = 0x00000080; -const STATUS_F2_PKT_AVAILABLE: u32 = 0x00000100; -const STATUS_F2_PKT_LEN_MASK: u32 = 0x000FFE00; -const STATUS_F2_PKT_LEN_SHIFT: u32 = 9; -const STATUS_F3_PKT_AVAILABLE: u32 = 0x00100000; -const STATUS_F3_PKT_LEN_MASK: u32 = 0xFFE00000; -const STATUS_F3_PKT_LEN_SHIFT: u32 = 21; + // SPI_STATUS_REGISTER bits + pub(crate) const STATUS_DATA_NOT_AVAILABLE: u32 = 0x00000001; + pub(crate) const STATUS_UNDERFLOW: u32 = 0x00000002; + pub(crate) const STATUS_OVERFLOW: u32 = 0x00000004; + pub(crate) const STATUS_F2_INTR: u32 = 0x00000008; + pub(crate) const STATUS_F3_INTR: u32 = 0x00000010; + pub(crate) const STATUS_F2_RX_READY: u32 = 0x00000020; + pub(crate) const STATUS_F3_RX_READY: u32 = 0x00000040; + pub(crate) const STATUS_HOST_CMD_DATA_ERR: u32 = 0x00000080; + pub(crate) const STATUS_F2_PKT_AVAILABLE: u32 = 0x00000100; + pub(crate) const STATUS_F2_PKT_LEN_MASK: u32 = 0x000FFE00; + pub(crate) const STATUS_F2_PKT_LEN_SHIFT: u32 = 9; + pub(crate) const STATUS_F3_PKT_AVAILABLE: u32 = 0x00100000; + pub(crate) const STATUS_F3_PKT_LEN_MASK: u32 = 0xFFE00000; + pub(crate) const STATUS_F3_PKT_LEN_SHIFT: u32 = 21; -const REG_BACKPLANE_GPIO_SELECT: u32 = 0x10005; -const REG_BACKPLANE_GPIO_OUTPUT: u32 = 0x10006; -const REG_BACKPLANE_GPIO_ENABLE: u32 = 0x10007; -const REG_BACKPLANE_FUNCTION2_WATERMARK: u32 = 0x10008; -const REG_BACKPLANE_DEVICE_CONTROL: u32 = 0x10009; -const REG_BACKPLANE_BACKPLANE_ADDRESS_LOW: u32 = 0x1000A; -const REG_BACKPLANE_BACKPLANE_ADDRESS_MID: u32 = 0x1000B; -const REG_BACKPLANE_BACKPLANE_ADDRESS_HIGH: u32 = 0x1000C; -const REG_BACKPLANE_FRAME_CONTROL: u32 = 0x1000D; -const REG_BACKPLANE_CHIP_CLOCK_CSR: u32 = 0x1000E; -const REG_BACKPLANE_PULL_UP: u32 = 0x1000F; -const REG_BACKPLANE_READ_FRAME_BC_LOW: u32 = 0x1001B; -const REG_BACKPLANE_READ_FRAME_BC_HIGH: u32 = 0x1001C; -const REG_BACKPLANE_WAKEUP_CTRL: u32 = 0x1001E; -const REG_BACKPLANE_SLEEP_CSR: u32 = 0x1001F; + pub(crate) const REG_BACKPLANE_GPIO_SELECT: u32 = 0x10005; + pub(crate) const REG_BACKPLANE_GPIO_OUTPUT: u32 = 0x10006; + pub(crate) const REG_BACKPLANE_GPIO_ENABLE: u32 = 0x10007; + pub(crate) const REG_BACKPLANE_FUNCTION2_WATERMARK: u32 = 0x10008; + pub(crate) const REG_BACKPLANE_DEVICE_CONTROL: u32 = 0x10009; + pub(crate) const REG_BACKPLANE_BACKPLANE_ADDRESS_LOW: u32 = 0x1000A; + pub(crate) const REG_BACKPLANE_BACKPLANE_ADDRESS_MID: u32 = 0x1000B; + pub(crate) const REG_BACKPLANE_BACKPLANE_ADDRESS_HIGH: u32 = 0x1000C; + pub(crate) const REG_BACKPLANE_FRAME_CONTROL: u32 = 0x1000D; + pub(crate) const REG_BACKPLANE_CHIP_CLOCK_CSR: u32 = 0x1000E; + pub(crate) const REG_BACKPLANE_PULL_UP: u32 = 0x1000F; + pub(crate) const REG_BACKPLANE_READ_FRAME_BC_LOW: u32 = 0x1001B; + pub(crate) const REG_BACKPLANE_READ_FRAME_BC_HIGH: u32 = 0x1001C; + pub(crate) const REG_BACKPLANE_WAKEUP_CTRL: u32 = 0x1001E; + pub(crate) const REG_BACKPLANE_SLEEP_CSR: u32 = 0x1001F; -const BACKPLANE_WINDOW_SIZE: usize = 0x8000; -const BACKPLANE_ADDRESS_MASK: u32 = 0x7FFF; -const BACKPLANE_ADDRESS_32BIT_FLAG: u32 = 0x08000; -const BACKPLANE_MAX_TRANSFER_SIZE: usize = 64; -// Active Low Power (ALP) clock constants -const BACKPLANE_ALP_AVAIL_REQ: u8 = 0x08; -const BACKPLANE_ALP_AVAIL: u8 = 0x40; + pub(crate) const BACKPLANE_WINDOW_SIZE: usize = 0x8000; + pub(crate) const BACKPLANE_ADDRESS_MASK: u32 = 0x7FFF; + pub(crate) const BACKPLANE_ADDRESS_32BIT_FLAG: u32 = 0x08000; + pub(crate) const BACKPLANE_MAX_TRANSFER_SIZE: usize = 64; + // Active Low Power (ALP) clock constants + pub(crate) const BACKPLANE_ALP_AVAIL_REQ: u8 = 0x08; + pub(crate) const BACKPLANE_ALP_AVAIL: u8 = 0x40; -// Broadcom AMBA (Advanced Microcontroller Bus Architecture) Interconnect (AI) -// constants -const AI_IOCTRL_OFFSET: u32 = 0x408; -const AI_IOCTRL_BIT_FGC: u8 = 0x0002; -const AI_IOCTRL_BIT_CLOCK_EN: u8 = 0x0001; -const AI_IOCTRL_BIT_CPUHALT: u8 = 0x0020; + // Broadcom AMBA (Advanced Microcontroller Bus Architecture) Interconnect + // (AI) pub (crate) constants + pub(crate) const AI_IOCTRL_OFFSET: u32 = 0x408; + pub(crate) const AI_IOCTRL_BIT_FGC: u8 = 0x0002; + pub(crate) const AI_IOCTRL_BIT_CLOCK_EN: u8 = 0x0001; + pub(crate) const AI_IOCTRL_BIT_CPUHALT: u8 = 0x0020; -const AI_RESETCTRL_OFFSET: u32 = 0x800; -const AI_RESETCTRL_BIT_RESET: u8 = 1; + pub(crate) const AI_RESETCTRL_OFFSET: u32 = 0x800; + pub(crate) const AI_RESETCTRL_BIT_RESET: u8 = 1; -const AI_RESETSTATUS_OFFSET: u32 = 0x804; + pub(crate) const AI_RESETSTATUS_OFFSET: u32 = 0x804; -const TEST_PATTERN: u32 = 0x12345678; -const FEEDBEAD: u32 = 0xFEEDBEAD; + pub(crate) const TEST_PATTERN: u32 = 0x12345678; + pub(crate) const FEEDBEAD: u32 = 0xFEEDBEAD; -// SPI_INTERRUPT_REGISTER and SPI_INTERRUPT_ENABLE_REGISTER Bits -const IRQ_DATA_UNAVAILABLE: u16 = 0x0001; // Requested data not available; Clear by writing a "1" -const IRQ_F2_F3_FIFO_RD_UNDERFLOW: u16 = 0x0002; -const IRQ_F2_F3_FIFO_WR_OVERFLOW: u16 = 0x0004; -const IRQ_COMMAND_ERROR: u16 = 0x0008; // Cleared by writing 1 -const IRQ_DATA_ERROR: u16 = 0x0010; // Cleared by writing 1 -const IRQ_F2_PACKET_AVAILABLE: u16 = 0x0020; -const IRQ_F3_PACKET_AVAILABLE: u16 = 0x0040; -const IRQ_F1_OVERFLOW: u16 = 0x0080; // Due to last write. Bkplane has pending write requests -const IRQ_MISC_INTR0: u16 = 0x0100; -const IRQ_MISC_INTR1: u16 = 0x0200; -const IRQ_MISC_INTR2: u16 = 0x0400; -const IRQ_MISC_INTR3: u16 = 0x0800; -const IRQ_MISC_INTR4: u16 = 0x1000; -const IRQ_F1_INTR: u16 = 0x2000; -const IRQ_F2_INTR: u16 = 0x4000; -const IRQ_F3_INTR: u16 = 0x8000; + // SPI_INTERRUPT_REGISTER and SPI_INTERRUPT_ENABLE_REGISTER Bits + pub(crate) const IRQ_DATA_UNAVAILABLE: u16 = 0x0001; // Requested data not available; Clear by writing a "1" + pub(crate) const IRQ_F2_F3_FIFO_RD_UNDERFLOW: u16 = 0x0002; + pub(crate) const IRQ_F2_F3_FIFO_WR_OVERFLOW: u16 = 0x0004; + pub(crate) const IRQ_COMMAND_ERROR: u16 = 0x0008; // Cleared by writing 1 + pub(crate) const IRQ_DATA_ERROR: u16 = 0x0010; // Cleared by writing 1 + pub(crate) const IRQ_F2_PACKET_AVAILABLE: u16 = 0x0020; + pub(crate) const IRQ_F3_PACKET_AVAILABLE: u16 = 0x0040; + pub(crate) const IRQ_F1_OVERFLOW: u16 = 0x0080; // Due to last write. Bkplane has pending write requests + pub(crate) const IRQ_MISC_INTR0: u16 = 0x0100; + pub(crate) const IRQ_MISC_INTR1: u16 = 0x0200; + pub(crate) const IRQ_MISC_INTR2: u16 = 0x0400; + pub(crate) const IRQ_MISC_INTR3: u16 = 0x0800; + pub(crate) const IRQ_MISC_INTR4: u16 = 0x1000; + pub(crate) const IRQ_F1_INTR: u16 = 0x2000; + pub(crate) const IRQ_F2_INTR: u16 = 0x4000; + pub(crate) const IRQ_F3_INTR: u16 = 0x8000; -const IOCTL_CMD_UP: u32 = 2; -const IOCTL_CMD_SET_SSID: u32 = 26; -const IOCTL_CMD_ANTDIV: u32 = 64; -const IOCTL_CMD_SET_VAR: u32 = 263; -const IOCTL_CMD_GET_VAR: u32 = 262; -const IOCTL_CMD_SET_PASSPHRASE: u32 = 268; + pub(crate) const IOCTL_CMD_UP: u32 = 2; + pub(crate) const IOCTL_CMD_SET_SSID: u32 = 26; + pub(crate) const IOCTL_CMD_ANTDIV: u32 = 64; + pub(crate) const IOCTL_CMD_SET_VAR: u32 = 263; + pub(crate) const IOCTL_CMD_GET_VAR: u32 = 262; + pub(crate) const IOCTL_CMD_SET_PASSPHRASE: u32 = 268; -const CHANNEL_TYPE_CONTROL: u8 = 0; -const CHANNEL_TYPE_EVENT: u8 = 1; -const CHANNEL_TYPE_DATA: u8 = 2; + pub(crate) const CHANNEL_TYPE_CONTROL: u8 = 0; + pub(crate) const CHANNEL_TYPE_EVENT: u8 = 1; + pub(crate) const CHANNEL_TYPE_DATA: u8 = 2; + + // CYW_SPID command structure constants. + pub(crate) const WRITE: bool = true; + pub(crate) const READ: bool = false; + pub(crate) const INC_ADDR: bool = true; + pub(crate) const FIXED_ADDR: bool = false; +} +use crate::constants::*; #[derive(Clone, Copy)] pub enum IoctlType { @@ -153,6 +156,7 @@ pub enum IoctlType { Set = 2, } +#[allow(unused)] #[derive(Clone, Copy, PartialEq, Eq)] enum Core { WLAN = 0, @@ -170,6 +174,7 @@ impl Core { } } +#[allow(unused)] struct Chip { arm_core_base_address: u32, socsram_base_address: u32, @@ -1077,6 +1082,7 @@ where true } + #[allow(unused)] async fn bp_read(&mut self, mut addr: u32, mut data: &mut [u32]) { // It seems the HW force-aligns the addr // to 2 if data.len() >= 2 @@ -1170,10 +1176,12 @@ where self.backplane_readn(addr, 2).await as u16 } + #[allow(unused)] async fn bp_write16(&mut self, addr: u32, val: u16) { self.backplane_writen(addr, val as u32, 2).await } + #[allow(unused)] async fn bp_read32(&mut self, addr: u32) -> u32 { self.backplane_readn(addr, 4).await } @@ -1244,6 +1252,7 @@ where self.readn(func, addr, 2).await as u16 } + #[allow(unused)] async fn write16(&mut self, func: u32, addr: u32, val: u16) { self.writen(func, addr, val as u32, 2).await } @@ -1252,6 +1261,7 @@ where self.readn(func, addr, 4).await } + #[allow(unused)] async fn write32(&mut self, func: u32, addr: u32, val: u32) { self.writen(func, addr, val, 4).await } diff --git a/src/structs.rs b/src/structs.rs index 35547097..ed5fc18d 100644 --- a/src/structs.rs +++ b/src/structs.rs @@ -5,10 +5,12 @@ macro_rules! impl_bytes { 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 { unsafe { core::mem::transmute(*bytes) } } @@ -167,6 +169,7 @@ pub struct DownloadHeader { } impl_bytes!(DownloadHeader); +#[allow(unused)] pub const DOWNLOAD_FLAG_NO_CRC: u16 = 0x0001; pub const DOWNLOAD_FLAG_BEGIN: u16 = 0x0002; pub const DOWNLOAD_FLAG_END: u16 = 0x0004; From 3b04ef265c0f47b160ca8e89ef0b8fefc4b7e6ec Mon Sep 17 00:00:00 2001 From: Daniel Bevenius Date: Mon, 26 Sep 2022 14:53:37 +0200 Subject: [PATCH 045/178] Add constants for BDC_VERSION This commit adds two constants intended to be used with the bdc_header.flags field. I believe these are the correct values after looking at following lines in `whd_cdc_bdc.c`: https://github.com/Infineon/wifi-host-driver/blob/40a7ec2273a950fbf89353d3eac98c5c1c2fd8cd/WiFi_Host_Driver/src/whd_cdc_bdc.c#L34-L35 https://github.com/Infineon/wifi-host-driver/blob/40a7ec2273a950fbf89353d3eac98c5c1c2fd8cd/WiFi_Host_Driver/src/whd_cdc_bdc.c#L447 --- src/lib.rs | 2 +- src/structs.rs | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index b8ce2e2a..d446313c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -800,7 +800,7 @@ where }; let bcd_header = BcdHeader { - flags: 0x20, + flags: BDC_VERSION << BDC_VERSION_SHIFT, priority: 0, flags2: 0, data_offset: 0, diff --git a/src/structs.rs b/src/structs.rs index ed5fc18d..6d4525a4 100644 --- a/src/structs.rs +++ b/src/structs.rs @@ -53,6 +53,9 @@ pub struct CdcHeader { } impl_bytes!(CdcHeader); +pub const BDC_VERSION: u8 = 2; +pub const BDC_VERSION_SHIFT: u8 = 4; + #[derive(Clone, Copy)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] #[repr(C)] From c385bbf07dfcadb832d02e91385bcecc45c0ef58 Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Sun, 2 Oct 2022 21:28:34 +0200 Subject: [PATCH 046/178] Update embassy, embedded-hal. --- Cargo.toml | 4 ++-- examples/rpi-pico-w/Cargo.toml | 20 ++++++++++---------- examples/rpi-pico-w/src/main.rs | 10 +++++----- rust-toolchain.toml | 2 +- src/lib.rs | 2 +- 5 files changed, 19 insertions(+), 19 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index cb6aa0b2..30c0da07 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,6 +21,6 @@ cortex-m = "0.7.3" cortex-m-rt = "0.7.0" futures = { version = "0.3.17", default-features = false, features = ["async-await", "cfg-target-has-atomic", "unstable"] } -embedded-hal-1 = { package = "embedded-hal", version = "1.0.0-alpha.8" } -embedded-hal-async = { version = "0.1.0-alpha.1" } +embedded-hal-1 = { package = "embedded-hal", version = "1.0.0-alpha.9" } +embedded-hal-async = { version = "0.1.0-alpha.2" } num_enum = { version = "0.5.7", default-features = false } diff --git a/examples/rpi-pico-w/Cargo.toml b/examples/rpi-pico-w/Cargo.toml index 53e72498..e82d12eb 100644 --- a/examples/rpi-pico-w/Cargo.toml +++ b/examples/rpi-pico-w/Cargo.toml @@ -8,8 +8,8 @@ edition = "2021" cyw43 = { path = "../../", features = ["defmt"]} embassy-executor = { version = "0.1.0", features = ["defmt", "integrated-timers"] } embassy-time = { version = "0.1.0", features = ["defmt", "defmt-timestamp-uptime"] } -embassy-rp = { version = "0.1.0", features = ["defmt", "unstable-traits", "nightly", "unstable-pac"] } -embassy-net = { version = "0.1.0", features = ["defmt", "tcp", "dhcpv4", "medium-ethernet", "pool-16"] } +embassy-rp = { version = "0.1.0", features = ["defmt", "unstable-traits", "nightly", "unstable-pac", "time-driver"] } +embassy-net = { version = "0.1.0", features = ["defmt", "tcp", "dhcpv4", "medium-ethernet", "pool-16", "unstable-traits", "nightly"] } atomic-polyfill = "0.1.5" static_cell = "1.0" @@ -21,19 +21,19 @@ cortex-m = { version = "0.7.6", features = ["critical-section-single-core"]} cortex-m-rt = "0.7.0" futures = { version = "0.3.17", default-features = false, features = ["async-await", "cfg-target-has-atomic", "unstable"] } -embedded-hal-1 = { package = "embedded-hal", version = "1.0.0-alpha.8" } -embedded-hal-async = { version = "0.1.0-alpha.1" } +embedded-hal-1 = { package = "embedded-hal", version = "1.0.0-alpha.9" } +embedded-hal-async = { version = "0.1.0-alpha.2" } embedded-io = { version = "0.3.0", features = ["async", "defmt"] } heapless = "0.7.15" [patch.crates-io] -embassy-executor = { git = "https://github.com/embassy-rs/embassy", rev = "cb9f0ef5b800ce4a22cde1805e0eb88425f1e07b" } -embassy-time = { git = "https://github.com/embassy-rs/embassy", rev = "cb9f0ef5b800ce4a22cde1805e0eb88425f1e07b" } -embassy-futures = { git = "https://github.com/embassy-rs/embassy", rev = "cb9f0ef5b800ce4a22cde1805e0eb88425f1e07b" } -embassy-sync = { git = "https://github.com/embassy-rs/embassy", rev = "cb9f0ef5b800ce4a22cde1805e0eb88425f1e07b" } -embassy-rp = { git = "https://github.com/embassy-rs/embassy", rev = "cb9f0ef5b800ce4a22cde1805e0eb88425f1e07b" } -embassy-net = { git = "https://github.com/embassy-rs/embassy", rev = "cb9f0ef5b800ce4a22cde1805e0eb88425f1e07b" } +embassy-executor = { git = "https://github.com/embassy-rs/embassy", rev = "73208d524843ca451b4cbfdb06e35f1b85290f4c" } +embassy-time = { git = "https://github.com/embassy-rs/embassy", rev = "73208d524843ca451b4cbfdb06e35f1b85290f4c" } +embassy-futures = { git = "https://github.com/embassy-rs/embassy", rev = "73208d524843ca451b4cbfdb06e35f1b85290f4c" } +embassy-sync = { git = "https://github.com/embassy-rs/embassy", rev = "73208d524843ca451b4cbfdb06e35f1b85290f4c" } +embassy-rp = { git = "https://github.com/embassy-rs/embassy", rev = "73208d524843ca451b4cbfdb06e35f1b85290f4c" } +embassy-net = { git = "https://github.com/embassy-rs/embassy", rev = "73208d524843ca451b4cbfdb06e35f1b85290f4c" } [profile.dev] debug = 2 diff --git a/examples/rpi-pico-w/src/main.rs b/examples/rpi-pico-w/src/main.rs index 986474ce..0915ef6b 100644 --- a/examples/rpi-pico-w/src/main.rs +++ b/examples/rpi-pico-w/src/main.rs @@ -1,6 +1,6 @@ #![no_std] #![no_main] -#![feature(generic_associated_types, type_alias_impl_trait)] +#![feature(type_alias_impl_trait)] use core::convert::Infallible; use core::future::Future; @@ -44,15 +44,15 @@ async fn main(spawner: Spawner) { let p = embassy_rp::init(Default::default()); // Include the WiFi firmware and Country Locale Matrix (CLM) blobs. - let fw = include_bytes!("../../../firmware/43439A0.bin"); - let clm = include_bytes!("../../../firmware/43439A0_clm.bin"); + //let fw = include_bytes!("../../../firmware/43439A0.bin"); + //let clm = include_bytes!("../../../firmware/43439A0_clm.bin"); // To make flashing faster for development, you may want to flash the firmwares independently // at hardcoded addresses, instead of baking them into the program with `include_bytes!`: // probe-rs-cli download 43439A0.bin --format bin --chip RP2040 --base-address 0x10100000 // probe-rs-cli download 43439A0.clm_blob --format bin --chip RP2040 --base-address 0x10140000 - //let fw = unsafe { core::slice::from_raw_parts(0x10100000 as *const u8, 224190) }; - //let clm = unsafe { core::slice::from_raw_parts(0x10140000 as *const u8, 4752) }; + let fw = unsafe { core::slice::from_raw_parts(0x10100000 as *const u8, 224190) }; + let clm = unsafe { core::slice::from_raw_parts(0x10140000 as *const u8, 4752) }; let pwr = Output::new(p.PIN_23, Level::Low); let cs = Output::new(p.PIN_25, Level::High); diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 0fa7cf7b..a35a11b8 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,7 +1,7 @@ # Before upgrading check that everything is available on all tier1 targets here: # https://rust-lang.github.io/rustup-components-history [toolchain] -channel = "nightly-2022-07-13" +channel = "nightly-2022-09-22" components = [ "rust-src", "rustfmt" ] targets = [ "thumbv6m-none-eabi", diff --git a/src/lib.rs b/src/lib.rs index d446313c..2a3d4dee 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -22,7 +22,7 @@ use embassy_net::{PacketBoxExt, PacketBuf}; use embassy_sync::blocking_mutex::raw::NoopRawMutex; use embassy_sync::channel::Channel; use embassy_time::{block_for, Duration, Timer}; -use embedded_hal_1::digital::blocking::OutputPin; +use embedded_hal_1::digital::OutputPin; use embedded_hal_async::spi::{SpiBusRead, SpiBusWrite, SpiDevice}; use self::structs::*; From 0d84533bcb25a0d6cbe6aee5418e1ebf2cbbc26a Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Tue, 20 Sep 2022 22:01:24 +0200 Subject: [PATCH 047/178] Use async spi transaction helper macro. --- src/lib.rs | 176 +++++++++++++++++++++-------------------------------- 1 file changed, 70 insertions(+), 106 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 2a3d4dee..ba8acd34 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -23,7 +23,7 @@ use embassy_sync::blocking_mutex::raw::NoopRawMutex; use embassy_sync::channel::Channel; use embassy_time::{block_for, Duration, Timer}; use embedded_hal_1::digital::OutputPin; -use embedded_hal_async::spi::{SpiBusRead, SpiBusWrite, SpiDevice}; +use embedded_hal_async::spi::{transaction, SpiBusRead, SpiBusWrite, SpiDevice}; use self::structs::*; use crate::events::Event; @@ -753,17 +753,13 @@ where let cmd = cmd_word(READ, INC_ADDR, FUNC_WLAN, 0, len); - self.spi - .transaction(|bus| { - let bus = unsafe { &mut *bus }; - async { - bus.write(&[cmd]).await?; - bus.read(&mut buf[..(len as usize + 3) / 4]).await?; - Ok(()) - } - }) - .await - .unwrap(); + transaction!(&mut self.spi, |bus| async { + bus.write(&[cmd]).await?; + bus.read(&mut buf[..(len as usize + 3) / 4]).await?; + Ok(()) + }) + .await + .unwrap(); trace!("rx {:02x}", &slice8_mut(&mut buf)[..(len as usize).min(48)]); @@ -817,17 +813,13 @@ where trace!(" {:02x}", &buf8[..total_len.min(48)]); let cmd = cmd_word(WRITE, INC_ADDR, FUNC_WLAN, 0, total_len as _); - self.spi - .transaction(|bus| { - let bus = unsafe { &mut *bus }; - async { - bus.write(&[cmd]).await?; - bus.write(&buf[..(total_len / 4)]).await?; - Ok(()) - } - }) - .await - .unwrap(); + transaction!(&mut self.spi, |bus| async { + bus.write(&[cmd]).await?; + bus.write(&buf[..(total_len / 4)]).await?; + Ok(()) + }) + .await + .unwrap(); } fn rx(&mut self, packet: &[u8]) { @@ -1012,17 +1004,13 @@ where let cmd = cmd_word(WRITE, INC_ADDR, FUNC_WLAN, 0, total_len as _); - self.spi - .transaction(|bus| { - let bus = unsafe { &mut *bus }; - async { - bus.write(&[cmd]).await?; - bus.write(&buf[..total_len / 4]).await?; - Ok(()) - } - }) - .await - .unwrap(); + transaction!(&mut self.spi, |bus| async { + bus.write(&[cmd]).await?; + bus.write(&buf[..total_len / 4]).await?; + Ok(()) + }) + .await + .unwrap(); } async fn core_disable(&mut self, core: Core) { @@ -1101,23 +1089,19 @@ where let cmd = cmd_word(READ, INC_ADDR, FUNC_BACKPLANE, window_offs, len as u32); - self.spi - .transaction(|bus| { - let bus = unsafe { &mut *bus }; - async { - bus.write(&[cmd]).await?; + transaction!(&mut self.spi, |bus| async { + bus.write(&[cmd]).await?; - // 4-byte response delay. - let mut junk = [0; 1]; - bus.read(&mut junk).await?; + // 4-byte response delay. + let mut junk = [0; 1]; + bus.read(&mut junk).await?; - // Read data - bus.read(&mut data[..len / 4]).await?; - Ok(()) - } - }) - .await - .unwrap(); + // Read data + bus.read(&mut data[..len / 4]).await?; + Ok(()) + }) + .await + .unwrap(); // Advance ptr. addr += len as u32; @@ -1146,17 +1130,13 @@ where let cmd = cmd_word(WRITE, INC_ADDR, FUNC_BACKPLANE, window_offs, len as u32); - self.spi - .transaction(|bus| { - let bus = unsafe { &mut *bus }; - async { - bus.write(&[cmd]).await?; - bus.write(&buf[..(len + 3) / 4]).await?; - Ok(()) - } - }) - .await - .unwrap(); + transaction!(&mut self.spi, |bus| async { + bus.write(&[cmd]).await?; + bus.write(&buf[..(len + 3) / 4]).await?; + Ok(()) + }) + .await + .unwrap(); // Advance ptr. addr += len as u32; @@ -1270,21 +1250,17 @@ where let cmd = cmd_word(READ, INC_ADDR, func, addr, len); let mut buf = [0; 1]; - self.spi - .transaction(|bus| { - let bus = unsafe { &mut *bus }; - async { - bus.write(&[cmd]).await?; - if func == FUNC_BACKPLANE { - // 4-byte response delay. - bus.read(&mut buf).await?; - } - bus.read(&mut buf).await?; - Ok(()) - } - }) - .await - .unwrap(); + transaction!(&mut self.spi, |bus| async { + bus.write(&[cmd]).await?; + if func == FUNC_BACKPLANE { + // 4-byte response delay. + bus.read(&mut buf).await?; + } + bus.read(&mut buf).await?; + Ok(()) + }) + .await + .unwrap(); buf[0] } @@ -1292,33 +1268,25 @@ where async fn writen(&mut self, func: u32, addr: u32, val: u32, len: u32) { let cmd = cmd_word(WRITE, INC_ADDR, func, addr, len); - self.spi - .transaction(|bus| { - let bus = unsafe { &mut *bus }; - async { - bus.write(&[cmd, val]).await?; - Ok(()) - } - }) - .await - .unwrap(); + transaction!(&mut self.spi, |bus| async { + bus.write(&[cmd, val]).await?; + Ok(()) + }) + .await + .unwrap(); } async fn read32_swapped(&mut self, addr: u32) -> u32 { let cmd = cmd_word(READ, INC_ADDR, FUNC_BUS, addr, 4); let mut buf = [0; 1]; - self.spi - .transaction(|bus| { - let bus = unsafe { &mut *bus }; - async { - bus.write(&[swap16(cmd)]).await?; - bus.read(&mut buf).await?; - Ok(()) - } - }) - .await - .unwrap(); + transaction!(&mut self.spi, |bus| async { + bus.write(&[swap16(cmd)]).await?; + bus.read(&mut buf).await?; + Ok(()) + }) + .await + .unwrap(); swap16(buf[0]) } @@ -1326,16 +1294,12 @@ where async fn write32_swapped(&mut self, addr: u32, val: u32) { let cmd = cmd_word(WRITE, INC_ADDR, FUNC_BUS, addr, 4); - self.spi - .transaction(|bus| { - let bus = unsafe { &mut *bus }; - async { - bus.write(&[swap16(cmd), swap16(val)]).await?; - Ok(()) - } - }) - .await - .unwrap(); + transaction!(&mut self.spi, |bus| async { + bus.write(&[swap16(cmd), swap16(val)]).await?; + Ok(()) + }) + .await + .unwrap(); } } From 27771e60afa0fe71c5512cee241400502e121b91 Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Mon, 7 Nov 2022 22:44:20 +0100 Subject: [PATCH 048/178] Bake the blob into the firmware by default. --- examples/rpi-pico-w/src/main.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/rpi-pico-w/src/main.rs b/examples/rpi-pico-w/src/main.rs index 0915ef6b..dbc7761c 100644 --- a/examples/rpi-pico-w/src/main.rs +++ b/examples/rpi-pico-w/src/main.rs @@ -44,15 +44,15 @@ async fn main(spawner: Spawner) { let p = embassy_rp::init(Default::default()); // Include the WiFi firmware and Country Locale Matrix (CLM) blobs. - //let fw = include_bytes!("../../../firmware/43439A0.bin"); - //let clm = include_bytes!("../../../firmware/43439A0_clm.bin"); + let fw = include_bytes!("../../../firmware/43439A0.bin"); + let clm = include_bytes!("../../../firmware/43439A0_clm.bin"); // To make flashing faster for development, you may want to flash the firmwares independently // at hardcoded addresses, instead of baking them into the program with `include_bytes!`: // probe-rs-cli download 43439A0.bin --format bin --chip RP2040 --base-address 0x10100000 // probe-rs-cli download 43439A0.clm_blob --format bin --chip RP2040 --base-address 0x10140000 - let fw = unsafe { core::slice::from_raw_parts(0x10100000 as *const u8, 224190) }; - let clm = unsafe { core::slice::from_raw_parts(0x10140000 as *const u8, 4752) }; + //let fw = unsafe { core::slice::from_raw_parts(0x10100000 as *const u8, 224190) }; + //let clm = unsafe { core::slice::from_raw_parts(0x10140000 as *const u8, 4752) }; let pwr = Output::new(p.PIN_23, Level::Low); let cs = Output::new(p.PIN_25, Level::High); From 8a81114baf4ffe12ec54e80e342f098c596177d1 Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Mon, 7 Nov 2022 22:51:58 +0100 Subject: [PATCH 049/178] Update Embassy, nightly, deps. --- Cargo.toml | 2 +- examples/rpi-pico-w/Cargo.toml | 14 +++++++------- examples/rpi-pico-w/src/main.rs | 6 +++--- rust-toolchain.toml | 2 +- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 30c0da07..8e1eddc1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,5 +22,5 @@ cortex-m-rt = "0.7.0" futures = { version = "0.3.17", default-features = false, features = ["async-await", "cfg-target-has-atomic", "unstable"] } embedded-hal-1 = { package = "embedded-hal", version = "1.0.0-alpha.9" } -embedded-hal-async = { version = "0.1.0-alpha.2" } +embedded-hal-async = { version = "0.1.0-alpha.3" } num_enum = { version = "0.5.7", default-features = false } diff --git a/examples/rpi-pico-w/Cargo.toml b/examples/rpi-pico-w/Cargo.toml index e82d12eb..7ba22a69 100644 --- a/examples/rpi-pico-w/Cargo.toml +++ b/examples/rpi-pico-w/Cargo.toml @@ -22,18 +22,18 @@ cortex-m-rt = "0.7.0" futures = { version = "0.3.17", default-features = false, features = ["async-await", "cfg-target-has-atomic", "unstable"] } embedded-hal-1 = { package = "embedded-hal", version = "1.0.0-alpha.9" } -embedded-hal-async = { version = "0.1.0-alpha.2" } +embedded-hal-async = { version = "0.1.0-alpha.3" } embedded-io = { version = "0.3.0", features = ["async", "defmt"] } heapless = "0.7.15" [patch.crates-io] -embassy-executor = { git = "https://github.com/embassy-rs/embassy", rev = "73208d524843ca451b4cbfdb06e35f1b85290f4c" } -embassy-time = { git = "https://github.com/embassy-rs/embassy", rev = "73208d524843ca451b4cbfdb06e35f1b85290f4c" } -embassy-futures = { git = "https://github.com/embassy-rs/embassy", rev = "73208d524843ca451b4cbfdb06e35f1b85290f4c" } -embassy-sync = { git = "https://github.com/embassy-rs/embassy", rev = "73208d524843ca451b4cbfdb06e35f1b85290f4c" } -embassy-rp = { git = "https://github.com/embassy-rs/embassy", rev = "73208d524843ca451b4cbfdb06e35f1b85290f4c" } -embassy-net = { git = "https://github.com/embassy-rs/embassy", rev = "73208d524843ca451b4cbfdb06e35f1b85290f4c" } +embassy-executor = { git = "https://github.com/embassy-rs/embassy", rev = "c53614f057cd7d9ac6e86aebd1fb6c1a1055d8b6" } +embassy-time = { git = "https://github.com/embassy-rs/embassy", rev = "c53614f057cd7d9ac6e86aebd1fb6c1a1055d8b6" } +embassy-futures = { git = "https://github.com/embassy-rs/embassy", rev = "c53614f057cd7d9ac6e86aebd1fb6c1a1055d8b6" } +embassy-sync = { git = "https://github.com/embassy-rs/embassy", rev = "c53614f057cd7d9ac6e86aebd1fb6c1a1055d8b6" } +embassy-rp = { git = "https://github.com/embassy-rs/embassy", rev = "c53614f057cd7d9ac6e86aebd1fb6c1a1055d8b6" } +embassy-net = { git = "https://github.com/embassy-rs/embassy", rev = "c53614f057cd7d9ac6e86aebd1fb6c1a1055d8b6" } [profile.dev] debug = 2 diff --git a/examples/rpi-pico-w/src/main.rs b/examples/rpi-pico-w/src/main.rs index dbc7761c..705c7acc 100644 --- a/examples/rpi-pico-w/src/main.rs +++ b/examples/rpi-pico-w/src/main.rs @@ -13,7 +13,7 @@ use embassy_rp::gpio::{Flex, Level, Output}; use embassy_rp::peripherals::{PIN_23, PIN_24, PIN_25, PIN_29}; use embedded_hal_1::spi::ErrorType; use embedded_hal_async::spi::{ExclusiveDevice, SpiBusFlush, SpiBusRead, SpiBusWrite}; -use embedded_io::asynch::{Read, Write}; +use embedded_io::asynch::Write; use static_cell::StaticCell; use {defmt_rtt as _, panic_probe as _}; @@ -165,7 +165,7 @@ impl SpiBusFlush for MySpi { } impl SpiBusRead for MySpi { - type ReadFuture<'a> = impl Future> + type ReadFuture<'a> = impl Future> + 'a where Self: 'a; @@ -195,7 +195,7 @@ impl SpiBusRead for MySpi { } impl SpiBusWrite for MySpi { - type WriteFuture<'a> = impl Future> + type WriteFuture<'a> = impl Future> + 'a where Self: 'a; diff --git a/rust-toolchain.toml b/rust-toolchain.toml index a35a11b8..3e219b0c 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,7 +1,7 @@ # Before upgrading check that everything is available on all tier1 targets here: # https://rust-lang.github.io/rustup-components-history [toolchain] -channel = "nightly-2022-09-22" +channel = "nightly-2022-10-25" components = [ "rust-src", "rustfmt" ] targets = [ "thumbv6m-none-eabi", From f4c9014fe4d5bb96f583d4b96122bcc536631d18 Mon Sep 17 00:00:00 2001 From: Ulf Lilleengen Date: Thu, 1 Dec 2022 22:09:45 +0100 Subject: [PATCH 050/178] feat: use async fn in trait --- Cargo.toml | 2 +- examples/rpi-pico-w/Cargo.toml | 16 +++--- examples/rpi-pico-w/src/main.rs | 95 ++++++++++++++------------------- rust-toolchain.toml | 2 +- 4 files changed, 50 insertions(+), 65 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 8e1eddc1..cc19c938 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,5 +22,5 @@ cortex-m-rt = "0.7.0" futures = { version = "0.3.17", default-features = false, features = ["async-await", "cfg-target-has-atomic", "unstable"] } embedded-hal-1 = { package = "embedded-hal", version = "1.0.0-alpha.9" } -embedded-hal-async = { version = "0.1.0-alpha.3" } +embedded-hal-async = { version = "0.2.0-alpha.0" } num_enum = { version = "0.5.7", default-features = false } diff --git a/examples/rpi-pico-w/Cargo.toml b/examples/rpi-pico-w/Cargo.toml index 7ba22a69..bb44667d 100644 --- a/examples/rpi-pico-w/Cargo.toml +++ b/examples/rpi-pico-w/Cargo.toml @@ -22,18 +22,18 @@ cortex-m-rt = "0.7.0" futures = { version = "0.3.17", default-features = false, features = ["async-await", "cfg-target-has-atomic", "unstable"] } embedded-hal-1 = { package = "embedded-hal", version = "1.0.0-alpha.9" } -embedded-hal-async = { version = "0.1.0-alpha.3" } -embedded-io = { version = "0.3.0", features = ["async", "defmt"] } +embedded-hal-async = { version = "0.2.0-alpha.0" } +embedded-io = { version = "0.4.0", features = ["async", "defmt"] } heapless = "0.7.15" [patch.crates-io] -embassy-executor = { git = "https://github.com/embassy-rs/embassy", rev = "c53614f057cd7d9ac6e86aebd1fb6c1a1055d8b6" } -embassy-time = { git = "https://github.com/embassy-rs/embassy", rev = "c53614f057cd7d9ac6e86aebd1fb6c1a1055d8b6" } -embassy-futures = { git = "https://github.com/embassy-rs/embassy", rev = "c53614f057cd7d9ac6e86aebd1fb6c1a1055d8b6" } -embassy-sync = { git = "https://github.com/embassy-rs/embassy", rev = "c53614f057cd7d9ac6e86aebd1fb6c1a1055d8b6" } -embassy-rp = { git = "https://github.com/embassy-rs/embassy", rev = "c53614f057cd7d9ac6e86aebd1fb6c1a1055d8b6" } -embassy-net = { git = "https://github.com/embassy-rs/embassy", rev = "c53614f057cd7d9ac6e86aebd1fb6c1a1055d8b6" } +embassy-executor = { git = "https://github.com/embassy-rs/embassy", rev = "645fb66a5122bdc8180e0e65d076ca103431a426" } +embassy-time = { git = "https://github.com/embassy-rs/embassy", rev = "645fb66a5122bdc8180e0e65d076ca103431a426" } +embassy-futures = { git = "https://github.com/embassy-rs/embassy", rev = "645fb66a5122bdc8180e0e65d076ca103431a426" } +embassy-sync = { git = "https://github.com/embassy-rs/embassy", rev = "645fb66a5122bdc8180e0e65d076ca103431a426" } +embassy-rp = { git = "https://github.com/embassy-rs/embassy", rev = "645fb66a5122bdc8180e0e65d076ca103431a426" } +embassy-net = { git = "https://github.com/embassy-rs/embassy", rev = "645fb66a5122bdc8180e0e65d076ca103431a426" } [profile.dev] debug = 2 diff --git a/examples/rpi-pico-w/src/main.rs b/examples/rpi-pico-w/src/main.rs index 705c7acc..a19f3859 100644 --- a/examples/rpi-pico-w/src/main.rs +++ b/examples/rpi-pico-w/src/main.rs @@ -1,9 +1,10 @@ #![no_std] #![no_main] #![feature(type_alias_impl_trait)] +#![feature(async_fn_in_trait)] +#![allow(incomplete_features)] use core::convert::Infallible; -use core::future::Future; use defmt::*; use embassy_executor::Spawner; @@ -155,74 +156,58 @@ impl ErrorType for MySpi { } impl SpiBusFlush for MySpi { - type FlushFuture<'a> = impl Future> - where - Self: 'a; - - fn flush<'a>(&'a mut self) -> Self::FlushFuture<'a> { - async move { Ok(()) } + async fn flush(&mut self) -> Result<(), Self::Error> { + Ok(()) } } impl SpiBusRead for MySpi { - type ReadFuture<'a> = impl Future> + 'a - where - Self: 'a; + async fn read(&mut self, words: &mut [u32]) -> Result<(), Self::Error> { + self.dio.set_as_input(); + for word in words { + let mut w = 0; + for _ in 0..32 { + w = w << 1; - fn read<'a>(&'a mut self, words: &'a mut [u32]) -> Self::ReadFuture<'a> { - async move { - self.dio.set_as_input(); - for word in words { - let mut w = 0; - for _ in 0..32 { - w = w << 1; - - // rising edge, sample data - if self.dio.is_high() { - w |= 0x01; - } - self.clk.set_high(); - - // falling edge - self.clk.set_low(); + // rising edge, sample data + if self.dio.is_high() { + w |= 0x01; } - *word = w - } + self.clk.set_high(); - Ok(()) + // falling edge + self.clk.set_low(); + } + *word = w } + + Ok(()) } } impl SpiBusWrite for MySpi { - type WriteFuture<'a> = impl Future> + 'a - where - Self: 'a; - - fn write<'a>(&'a mut self, words: &'a [u32]) -> Self::WriteFuture<'a> { - async move { - self.dio.set_as_output(); - for word in words { - let mut word = *word; - for _ in 0..32 { - // falling edge, setup data - self.clk.set_low(); - if word & 0x8000_0000 == 0 { - self.dio.set_low(); - } else { - self.dio.set_high(); - } - - // rising edge - self.clk.set_high(); - - word = word << 1; + async fn write(&mut self, words: &[u32]) -> Result<(), Self::Error> { + self.dio.set_as_output(); + for word in words { + let mut word = *word; + for _ in 0..32 { + // falling edge, setup data + self.clk.set_low(); + if word & 0x8000_0000 == 0 { + self.dio.set_low(); + } else { + self.dio.set_high(); } - } - self.clk.set_low(); - self.dio.set_as_input(); - Ok(()) + // rising edge + self.clk.set_high(); + + word = word << 1; + } } + self.clk.set_low(); + + self.dio.set_as_input(); + Ok(()) } } diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 3e219b0c..ffbcbd6f 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,7 +1,7 @@ # Before upgrading check that everything is available on all tier1 targets here: # https://rust-lang.github.io/rustup-components-history [toolchain] -channel = "nightly-2022-10-25" +channel = "nightly-2022-11-22" components = [ "rust-src", "rustfmt" ] targets = [ "thumbv6m-none-eabi", From 56b50f8b62024ba5bfccfa69381e6d98f8b108b5 Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Sun, 25 Dec 2022 22:02:20 +0100 Subject: [PATCH 051/178] fix bp_read. It was broken since the switch from u8 to u32. --- src/lib.rs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index ba8acd34..8e30522b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1071,13 +1071,15 @@ where } #[allow(unused)] - async fn bp_read(&mut self, mut addr: u32, mut data: &mut [u32]) { + async fn bp_read(&mut self, mut addr: u32, mut data: &mut [u8]) { // It seems the HW force-aligns the addr // to 2 if data.len() >= 2 // to 4 if data.len() >= 4 // To simplify, enforce 4-align for now. assert!(addr % 4 == 0); + let mut buf = [0u32; BACKPLANE_MAX_TRANSFER_SIZE / 4]; + while !data.is_empty() { // Ensure transfer doesn't cross a window boundary. let window_offs = addr & BACKPLANE_ADDRESS_MASK; @@ -1097,15 +1099,17 @@ where bus.read(&mut junk).await?; // Read data - bus.read(&mut data[..len / 4]).await?; + bus.read(&mut buf[..(len + 3) / 4]).await?; Ok(()) }) .await .unwrap(); + data[..len].copy_from_slice(&slice8_mut(&mut buf)[..len]); + // Advance ptr. addr += len as u32; - data = &mut data[len / 4..]; + data = &mut data[len..]; } } From 42cc0c6d736f6d296ef2a6a636ddf8733cdcd7c6 Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Sun, 25 Dec 2022 22:02:50 +0100 Subject: [PATCH 052/178] print ioctl error as signed. --- src/lib.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 8e30522b..43082175 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -858,7 +858,10 @@ where if let IoctlState::Sent { buf } = self.state.ioctl_state.get() { if cdc_header.id == self.ioctl_id { - assert_eq!(cdc_header.status, 0); // todo propagate error instead + if cdc_header.status != 0 { + // TODO: propagate error instead + panic!("IOCTL error {=i32}", cdc_header.status as i32); + } let resp_len = cdc_header.len as usize; info!("IOCTL Response: {:02x}", &payload[CdcHeader::SIZE..][..resp_len]); From 076ada4c0233d2f89c89cda4c01910a86add90ac Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Sun, 25 Dec 2022 22:50:27 +0100 Subject: [PATCH 053/178] Add feature to display console logs from the wifi firmware. --- Cargo.toml | 3 ++ examples/rpi-pico-w/Cargo.toml | 2 +- src/lib.rs | 81 ++++++++++++++++++++++++++++++++++ src/structs.rs | 26 +++++++++++ 4 files changed, 111 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index cc19c938..dadfb5c5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,6 +7,9 @@ edition = "2021" defmt = ["dep:defmt"] log = ["dep:log"] +# Fetch console logs from the WiFi firmware and forward them to `log` or `defmt`. +firmware-logs = [] + [dependencies] embassy-time = { version = "0.1.0" } embassy-sync = { version = "0.1.0" } diff --git a/examples/rpi-pico-w/Cargo.toml b/examples/rpi-pico-w/Cargo.toml index bb44667d..b817289e 100644 --- a/examples/rpi-pico-w/Cargo.toml +++ b/examples/rpi-pico-w/Cargo.toml @@ -5,7 +5,7 @@ edition = "2021" [dependencies] -cyw43 = { path = "../../", features = ["defmt"]} +cyw43 = { path = "../../", features = ["defmt", "firmware-logs"]} embassy-executor = { version = "0.1.0", features = ["defmt", "integrated-timers"] } embassy-time = { version = "0.1.0", features = ["defmt", "defmt-timestamp-uptime"] } embassy-rp = { version = "0.1.0", features = ["defmt", "unstable-traits", "nightly", "unstable-pac", "time-driver"] } diff --git a/src/lib.rs b/src/lib.rs index 43082175..883e669d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -575,6 +575,17 @@ pub struct Runner<'a, PWR, SPI> { backplane_window: u32, sdpcm_seq_max: u8, + + #[cfg(feature = "firmware-logs")] + log: LogState, +} + +#[cfg(feature = "firmware-logs")] +struct LogState { + addr: u32, + last_idx: usize, + buf: [u8; 256], + buf_count: usize, } pub async fn new<'a, PWR, SPI>( @@ -598,6 +609,14 @@ where backplane_window: 0xAAAA_AAAA, sdpcm_seq_max: 1, + + #[cfg(feature = "firmware-logs")] + log: LogState { + addr: 0, + last_idx: 0, + buf: [0; 256], + buf_count: 0, + }, }; runner.init(firmware).await; @@ -715,12 +734,74 @@ where //while self.read8(FUNC_BACKPLANE, REG_BACKPLANE_CHIP_CLOCK_CSR).await & 0x80 == 0 {} //info!("clock ok"); + #[cfg(feature = "firmware-logs")] + self.log_init().await; + info!("init done "); } + #[cfg(feature = "firmware-logs")] + async fn log_init(&mut self) { + // Initialize shared memory for logging. + + let shared_addr = self + .bp_read32(CHIP.atcm_ram_base_address + CHIP.chip_ram_size - 4 - CHIP.socram_srmem_size) + .await; + info!("shared_addr {:08x}", shared_addr); + + let mut shared = [0; SharedMemData::SIZE]; + self.bp_read(shared_addr, &mut shared).await; + let shared = SharedMemData::from_bytes(&shared); + info!("shared: {:08x}", shared); + + self.log.addr = shared.console_addr + 8; + } + + #[cfg(feature = "firmware-logs")] + async fn log_read(&mut self) { + // Read log struct + let mut log = [0; SharedMemLog::SIZE]; + self.bp_read(self.log.addr, &mut log).await; + let log = SharedMemLog::from_bytes(&log); + + let idx = log.idx as usize; + + // If pointer hasn't moved, no need to do anything. + if idx == self.log.last_idx { + return; + } + + // Read entire buf for now. We could read only what we need, but then we + // run into annoying alignment issues in `bp_read`. + let mut buf = [0; 0x400]; + self.bp_read(log.buf, &mut buf).await; + + while self.log.last_idx != idx as usize { + let b = buf[self.log.last_idx]; + if b == b'\r' || b == b'\n' { + if self.log.buf_count != 0 { + let s = unsafe { core::str::from_utf8_unchecked(&self.log.buf[..self.log.buf_count]) }; + debug!("LOGS: {}", s); + self.log.buf_count = 0; + } + } else if self.log.buf_count < self.log.buf.len() { + self.log.buf[self.log.buf_count] = b; + self.log.buf_count += 1; + } + + self.log.last_idx += 1; + if self.log.last_idx == 0x400 { + self.log.last_idx = 0; + } + } + } + pub async fn run(mut self) -> ! { let mut buf = [0; 512]; loop { + #[cfg(feature = "firmware-logs")] + self.log_read().await; + // Send stuff // TODO flow control not yet complete if !self.has_credit() { diff --git a/src/structs.rs b/src/structs.rs index 6d4525a4..41a34066 100644 --- a/src/structs.rs +++ b/src/structs.rs @@ -18,6 +18,32 @@ macro_rules! impl_bytes { }; } +#[derive(Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[repr(C)] +pub struct SharedMemData { + pub flags: u32, + pub trap_addr: u32, + pub assert_exp_addr: u32, + pub assert_file_addr: u32, + pub assert_line: u32, + pub console_addr: u32, + pub msgtrace_addr: u32, + pub fwid: u32, +} +impl_bytes!(SharedMemData); + +#[derive(Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[repr(C)] +pub struct SharedMemLog { + pub buf: u32, + pub buf_size: u32, + pub idx: u32, + pub out_idx: u32, +} +impl_bytes!(SharedMemLog); + #[derive(Clone, Copy)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] #[repr(C)] From 1b6799d93f0bbd6154c124d51aa47aeed0acf15d Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Mon, 26 Dec 2022 23:21:58 +0100 Subject: [PATCH 054/178] split bus, consts into separate mods. --- src/bus.rs | 321 ++++++++++++++++++++++++++++++ src/consts.rs | 105 ++++++++++ src/lib.rs | 536 +++++++------------------------------------------- 3 files changed, 496 insertions(+), 466 deletions(-) create mode 100644 src/bus.rs create mode 100644 src/consts.rs diff --git a/src/bus.rs b/src/bus.rs new file mode 100644 index 00000000..f220cffc --- /dev/null +++ b/src/bus.rs @@ -0,0 +1,321 @@ +use core::slice; + +use embassy_time::{Duration, Timer}; +use embedded_hal_1::digital::OutputPin; +use embedded_hal_async::spi::{transaction, SpiBusRead, SpiBusWrite, SpiDevice}; + +use crate::consts::*; + +pub(crate) struct Bus { + backplane_window: u32, + pwr: PWR, + spi: SPI, +} + +impl Bus +where + PWR: OutputPin, + SPI: SpiDevice, + SPI::Bus: SpiBusRead + SpiBusWrite, +{ + pub(crate) fn new(pwr: PWR, spi: SPI) -> Self { + Self { + backplane_window: 0xAAAA_AAAA, + pwr, + spi, + } + } + + pub async fn init(&mut self) { + // Reset + self.pwr.set_low().unwrap(); + Timer::after(Duration::from_millis(20)).await; + self.pwr.set_high().unwrap(); + Timer::after(Duration::from_millis(250)).await; + + while self.read32_swapped(REG_BUS_TEST_RO).await != FEEDBEAD {} + + self.write32_swapped(REG_BUS_TEST_RW, TEST_PATTERN).await; + let val = self.read32_swapped(REG_BUS_TEST_RW).await; + assert_eq!(val, TEST_PATTERN); + + // 32-bit word length, little endian (which is the default endianess). + self.write32_swapped(REG_BUS_CTRL, WORD_LENGTH_32 | HIGH_SPEED).await; + + let val = self.read32(FUNC_BUS, REG_BUS_TEST_RO).await; + assert_eq!(val, FEEDBEAD); + let val = self.read32(FUNC_BUS, REG_BUS_TEST_RW).await; + assert_eq!(val, TEST_PATTERN); + } + + pub async fn wlan_read(&mut self, buf: &mut [u32]) { + let cmd = cmd_word(READ, INC_ADDR, FUNC_WLAN, 0, buf.len() as u32 * 4); + transaction!(&mut self.spi, |bus| async { + bus.write(&[cmd]).await?; + bus.read(buf).await?; + Ok(()) + }) + .await + .unwrap(); + } + + pub async fn wlan_write(&mut self, buf: &[u32]) { + let cmd = cmd_word(WRITE, INC_ADDR, FUNC_WLAN, 0, buf.len() as u32 * 4); + transaction!(&mut self.spi, |bus| async { + bus.write(&[cmd]).await?; + bus.write(buf).await?; + Ok(()) + }) + .await + .unwrap(); + } + + #[allow(unused)] + pub async fn bp_read(&mut self, mut addr: u32, mut data: &mut [u8]) { + // It seems the HW force-aligns the addr + // to 2 if data.len() >= 2 + // to 4 if data.len() >= 4 + // To simplify, enforce 4-align for now. + assert!(addr % 4 == 0); + + let mut buf = [0u32; BACKPLANE_MAX_TRANSFER_SIZE / 4]; + + while !data.is_empty() { + // Ensure transfer doesn't cross a window boundary. + let window_offs = addr & BACKPLANE_ADDRESS_MASK; + let window_remaining = BACKPLANE_WINDOW_SIZE - window_offs as usize; + + let len = data.len().min(BACKPLANE_MAX_TRANSFER_SIZE).min(window_remaining); + + self.backplane_set_window(addr).await; + + let cmd = cmd_word(READ, INC_ADDR, FUNC_BACKPLANE, window_offs, len as u32); + + transaction!(&mut self.spi, |bus| async { + bus.write(&[cmd]).await?; + + // 4-byte response delay. + let mut junk = [0; 1]; + bus.read(&mut junk).await?; + + // Read data + bus.read(&mut buf[..(len + 3) / 4]).await?; + Ok(()) + }) + .await + .unwrap(); + + data[..len].copy_from_slice(&slice8_mut(&mut buf)[..len]); + + // Advance ptr. + addr += len as u32; + data = &mut data[len..]; + } + } + + pub async fn bp_write(&mut self, mut addr: u32, mut data: &[u8]) { + // It seems the HW force-aligns the addr + // to 2 if data.len() >= 2 + // to 4 if data.len() >= 4 + // To simplify, enforce 4-align for now. + assert!(addr % 4 == 0); + + let mut buf = [0u32; BACKPLANE_MAX_TRANSFER_SIZE / 4]; + + while !data.is_empty() { + // Ensure transfer doesn't cross a window boundary. + let window_offs = addr & BACKPLANE_ADDRESS_MASK; + let window_remaining = BACKPLANE_WINDOW_SIZE - window_offs as usize; + + let len = data.len().min(BACKPLANE_MAX_TRANSFER_SIZE).min(window_remaining); + slice8_mut(&mut buf)[..len].copy_from_slice(&data[..len]); + + self.backplane_set_window(addr).await; + + let cmd = cmd_word(WRITE, INC_ADDR, FUNC_BACKPLANE, window_offs, len as u32); + + transaction!(&mut self.spi, |bus| async { + bus.write(&[cmd]).await?; + bus.write(&buf[..(len + 3) / 4]).await?; + Ok(()) + }) + .await + .unwrap(); + + // Advance ptr. + addr += len as u32; + data = &data[len..]; + } + } + + pub async fn bp_read8(&mut self, addr: u32) -> u8 { + self.backplane_readn(addr, 1).await as u8 + } + + pub async fn bp_write8(&mut self, addr: u32, val: u8) { + self.backplane_writen(addr, val as u32, 1).await + } + + pub async fn bp_read16(&mut self, addr: u32) -> u16 { + self.backplane_readn(addr, 2).await as u16 + } + + #[allow(unused)] + pub async fn bp_write16(&mut self, addr: u32, val: u16) { + self.backplane_writen(addr, val as u32, 2).await + } + + #[allow(unused)] + pub async fn bp_read32(&mut self, addr: u32) -> u32 { + self.backplane_readn(addr, 4).await + } + + pub async fn bp_write32(&mut self, addr: u32, val: u32) { + self.backplane_writen(addr, val, 4).await + } + + async fn backplane_readn(&mut self, addr: u32, len: u32) -> u32 { + self.backplane_set_window(addr).await; + + let mut bus_addr = addr & BACKPLANE_ADDRESS_MASK; + if len == 4 { + bus_addr |= BACKPLANE_ADDRESS_32BIT_FLAG + } + self.readn(FUNC_BACKPLANE, bus_addr, len).await + } + + async fn backplane_writen(&mut self, addr: u32, val: u32, len: u32) { + self.backplane_set_window(addr).await; + + let mut bus_addr = addr & BACKPLANE_ADDRESS_MASK; + if len == 4 { + bus_addr |= BACKPLANE_ADDRESS_32BIT_FLAG + } + self.writen(FUNC_BACKPLANE, bus_addr, val, len).await + } + + async fn backplane_set_window(&mut self, addr: u32) { + let new_window = addr & !BACKPLANE_ADDRESS_MASK; + + if (new_window >> 24) as u8 != (self.backplane_window >> 24) as u8 { + self.write8( + FUNC_BACKPLANE, + REG_BACKPLANE_BACKPLANE_ADDRESS_HIGH, + (new_window >> 24) as u8, + ) + .await; + } + if (new_window >> 16) as u8 != (self.backplane_window >> 16) as u8 { + self.write8( + FUNC_BACKPLANE, + REG_BACKPLANE_BACKPLANE_ADDRESS_MID, + (new_window >> 16) as u8, + ) + .await; + } + if (new_window >> 8) as u8 != (self.backplane_window >> 8) as u8 { + self.write8( + FUNC_BACKPLANE, + REG_BACKPLANE_BACKPLANE_ADDRESS_LOW, + (new_window >> 8) as u8, + ) + .await; + } + self.backplane_window = new_window; + } + + pub async fn read8(&mut self, func: u32, addr: u32) -> u8 { + self.readn(func, addr, 1).await as u8 + } + + pub async fn write8(&mut self, func: u32, addr: u32, val: u8) { + self.writen(func, addr, val as u32, 1).await + } + + pub async fn read16(&mut self, func: u32, addr: u32) -> u16 { + self.readn(func, addr, 2).await as u16 + } + + #[allow(unused)] + pub async fn write16(&mut self, func: u32, addr: u32, val: u16) { + self.writen(func, addr, val as u32, 2).await + } + + pub async fn read32(&mut self, func: u32, addr: u32) -> u32 { + self.readn(func, addr, 4).await + } + + #[allow(unused)] + pub async fn write32(&mut self, func: u32, addr: u32, val: u32) { + self.writen(func, addr, val, 4).await + } + + async fn readn(&mut self, func: u32, addr: u32, len: u32) -> u32 { + let cmd = cmd_word(READ, INC_ADDR, func, addr, len); + let mut buf = [0; 1]; + + transaction!(&mut self.spi, |bus| async { + bus.write(&[cmd]).await?; + if func == FUNC_BACKPLANE { + // 4-byte response delay. + bus.read(&mut buf).await?; + } + bus.read(&mut buf).await?; + Ok(()) + }) + .await + .unwrap(); + + buf[0] + } + + async fn writen(&mut self, func: u32, addr: u32, val: u32, len: u32) { + let cmd = cmd_word(WRITE, INC_ADDR, func, addr, len); + + transaction!(&mut self.spi, |bus| async { + bus.write(&[cmd, val]).await?; + Ok(()) + }) + .await + .unwrap(); + } + + async fn read32_swapped(&mut self, addr: u32) -> u32 { + let cmd = cmd_word(READ, INC_ADDR, FUNC_BUS, addr, 4); + let mut buf = [0; 1]; + + transaction!(&mut self.spi, |bus| async { + bus.write(&[swap16(cmd)]).await?; + bus.read(&mut buf).await?; + Ok(()) + }) + .await + .unwrap(); + + swap16(buf[0]) + } + + async fn write32_swapped(&mut self, addr: u32, val: u32) { + let cmd = cmd_word(WRITE, INC_ADDR, FUNC_BUS, addr, 4); + + transaction!(&mut self.spi, |bus| async { + bus.write(&[swap16(cmd), swap16(val)]).await?; + Ok(()) + }) + .await + .unwrap(); + } +} + +fn swap16(x: u32) -> u32 { + x.rotate_left(16) +} + +fn cmd_word(write: bool, incr: bool, func: u32, addr: u32, len: u32) -> u32 { + (write as u32) << 31 | (incr as u32) << 30 | (func & 0b11) << 28 | (addr & 0x1FFFF) << 11 | (len & 0x7FF) +} + +fn slice8_mut(x: &mut [u32]) -> &mut [u8] { + let len = x.len() * 4; + unsafe { slice::from_raw_parts_mut(x.as_mut_ptr() as _, len) } +} diff --git a/src/consts.rs b/src/consts.rs new file mode 100644 index 00000000..bee70660 --- /dev/null +++ b/src/consts.rs @@ -0,0 +1,105 @@ +#![allow(unused)] +pub(crate) const FUNC_BUS: u32 = 0; +pub(crate) const FUNC_BACKPLANE: u32 = 1; +pub(crate) const FUNC_WLAN: u32 = 2; +pub(crate) const FUNC_BT: u32 = 3; + +pub(crate) const REG_BUS_CTRL: u32 = 0x0; +pub(crate) const REG_BUS_INTERRUPT: u32 = 0x04; // 16 bits - Interrupt status +pub(crate) const REG_BUS_INTERRUPT_ENABLE: u32 = 0x06; // 16 bits - Interrupt mask +pub(crate) const REG_BUS_STATUS: u32 = 0x8; +pub(crate) const REG_BUS_TEST_RO: u32 = 0x14; +pub(crate) const REG_BUS_TEST_RW: u32 = 0x18; +pub(crate) const REG_BUS_RESP_DELAY: u32 = 0x1c; +pub(crate) const WORD_LENGTH_32: u32 = 0x1; +pub(crate) const HIGH_SPEED: u32 = 0x10; + +// SPI_STATUS_REGISTER bits +pub(crate) const STATUS_DATA_NOT_AVAILABLE: u32 = 0x00000001; +pub(crate) const STATUS_UNDERFLOW: u32 = 0x00000002; +pub(crate) const STATUS_OVERFLOW: u32 = 0x00000004; +pub(crate) const STATUS_F2_INTR: u32 = 0x00000008; +pub(crate) const STATUS_F3_INTR: u32 = 0x00000010; +pub(crate) const STATUS_F2_RX_READY: u32 = 0x00000020; +pub(crate) const STATUS_F3_RX_READY: u32 = 0x00000040; +pub(crate) const STATUS_HOST_CMD_DATA_ERR: u32 = 0x00000080; +pub(crate) const STATUS_F2_PKT_AVAILABLE: u32 = 0x00000100; +pub(crate) const STATUS_F2_PKT_LEN_MASK: u32 = 0x000FFE00; +pub(crate) const STATUS_F2_PKT_LEN_SHIFT: u32 = 9; +pub(crate) const STATUS_F3_PKT_AVAILABLE: u32 = 0x00100000; +pub(crate) const STATUS_F3_PKT_LEN_MASK: u32 = 0xFFE00000; +pub(crate) const STATUS_F3_PKT_LEN_SHIFT: u32 = 21; + +pub(crate) const REG_BACKPLANE_GPIO_SELECT: u32 = 0x10005; +pub(crate) const REG_BACKPLANE_GPIO_OUTPUT: u32 = 0x10006; +pub(crate) const REG_BACKPLANE_GPIO_ENABLE: u32 = 0x10007; +pub(crate) const REG_BACKPLANE_FUNCTION2_WATERMARK: u32 = 0x10008; +pub(crate) const REG_BACKPLANE_DEVICE_CONTROL: u32 = 0x10009; +pub(crate) const REG_BACKPLANE_BACKPLANE_ADDRESS_LOW: u32 = 0x1000A; +pub(crate) const REG_BACKPLANE_BACKPLANE_ADDRESS_MID: u32 = 0x1000B; +pub(crate) const REG_BACKPLANE_BACKPLANE_ADDRESS_HIGH: u32 = 0x1000C; +pub(crate) const REG_BACKPLANE_FRAME_CONTROL: u32 = 0x1000D; +pub(crate) const REG_BACKPLANE_CHIP_CLOCK_CSR: u32 = 0x1000E; +pub(crate) const REG_BACKPLANE_PULL_UP: u32 = 0x1000F; +pub(crate) const REG_BACKPLANE_READ_FRAME_BC_LOW: u32 = 0x1001B; +pub(crate) const REG_BACKPLANE_READ_FRAME_BC_HIGH: u32 = 0x1001C; +pub(crate) const REG_BACKPLANE_WAKEUP_CTRL: u32 = 0x1001E; +pub(crate) const REG_BACKPLANE_SLEEP_CSR: u32 = 0x1001F; + +pub(crate) const BACKPLANE_WINDOW_SIZE: usize = 0x8000; +pub(crate) const BACKPLANE_ADDRESS_MASK: u32 = 0x7FFF; +pub(crate) const BACKPLANE_ADDRESS_32BIT_FLAG: u32 = 0x08000; +pub(crate) const BACKPLANE_MAX_TRANSFER_SIZE: usize = 64; +// Active Low Power (ALP) clock constants +pub(crate) const BACKPLANE_ALP_AVAIL_REQ: u8 = 0x08; +pub(crate) const BACKPLANE_ALP_AVAIL: u8 = 0x40; + +// Broadcom AMBA (Advanced Microcontroller Bus Architecture) Interconnect +// (AI) pub (crate) constants +pub(crate) const AI_IOCTRL_OFFSET: u32 = 0x408; +pub(crate) const AI_IOCTRL_BIT_FGC: u8 = 0x0002; +pub(crate) const AI_IOCTRL_BIT_CLOCK_EN: u8 = 0x0001; +pub(crate) const AI_IOCTRL_BIT_CPUHALT: u8 = 0x0020; + +pub(crate) const AI_RESETCTRL_OFFSET: u32 = 0x800; +pub(crate) const AI_RESETCTRL_BIT_RESET: u8 = 1; + +pub(crate) const AI_RESETSTATUS_OFFSET: u32 = 0x804; + +pub(crate) const TEST_PATTERN: u32 = 0x12345678; +pub(crate) const FEEDBEAD: u32 = 0xFEEDBEAD; + +// SPI_INTERRUPT_REGISTER and SPI_INTERRUPT_ENABLE_REGISTER Bits +pub(crate) const IRQ_DATA_UNAVAILABLE: u16 = 0x0001; // Requested data not available; Clear by writing a "1" +pub(crate) const IRQ_F2_F3_FIFO_RD_UNDERFLOW: u16 = 0x0002; +pub(crate) const IRQ_F2_F3_FIFO_WR_OVERFLOW: u16 = 0x0004; +pub(crate) const IRQ_COMMAND_ERROR: u16 = 0x0008; // Cleared by writing 1 +pub(crate) const IRQ_DATA_ERROR: u16 = 0x0010; // Cleared by writing 1 +pub(crate) const IRQ_F2_PACKET_AVAILABLE: u16 = 0x0020; +pub(crate) const IRQ_F3_PACKET_AVAILABLE: u16 = 0x0040; +pub(crate) const IRQ_F1_OVERFLOW: u16 = 0x0080; // Due to last write. Bkplane has pending write requests +pub(crate) const IRQ_MISC_INTR0: u16 = 0x0100; +pub(crate) const IRQ_MISC_INTR1: u16 = 0x0200; +pub(crate) const IRQ_MISC_INTR2: u16 = 0x0400; +pub(crate) const IRQ_MISC_INTR3: u16 = 0x0800; +pub(crate) const IRQ_MISC_INTR4: u16 = 0x1000; +pub(crate) const IRQ_F1_INTR: u16 = 0x2000; +pub(crate) const IRQ_F2_INTR: u16 = 0x4000; +pub(crate) const IRQ_F3_INTR: u16 = 0x8000; + +pub(crate) const IOCTL_CMD_UP: u32 = 2; +pub(crate) const IOCTL_CMD_SET_SSID: u32 = 26; +pub(crate) const IOCTL_CMD_ANTDIV: u32 = 64; +pub(crate) const IOCTL_CMD_SET_VAR: u32 = 263; +pub(crate) const IOCTL_CMD_GET_VAR: u32 = 262; +pub(crate) const IOCTL_CMD_SET_PASSPHRASE: u32 = 268; + +pub(crate) const CHANNEL_TYPE_CONTROL: u8 = 0; +pub(crate) const CHANNEL_TYPE_EVENT: u8 = 1; +pub(crate) const CHANNEL_TYPE_DATA: u8 = 2; + +// CYW_SPID command structure constants. +pub(crate) const WRITE: bool = true; +pub(crate) const READ: bool = false; +pub(crate) const INC_ADDR: bool = true; +pub(crate) const FIXED_ADDR: bool = false; diff --git a/src/lib.rs b/src/lib.rs index 883e669d..fa73b32e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,6 +6,8 @@ // This mod MUST go first, so that the others see its macros. pub(crate) mod fmt; +mod bus; +mod consts; mod countries; mod events; mod structs; @@ -23,132 +25,12 @@ use embassy_sync::blocking_mutex::raw::NoopRawMutex; use embassy_sync::channel::Channel; use embassy_time::{block_for, Duration, Timer}; use embedded_hal_1::digital::OutputPin; -use embedded_hal_async::spi::{transaction, SpiBusRead, SpiBusWrite, SpiDevice}; +use embedded_hal_async::spi::{SpiBusRead, SpiBusWrite, SpiDevice}; -use self::structs::*; +use crate::bus::Bus; +use crate::consts::*; use crate::events::Event; - -fn swap16(x: u32) -> u32 { - x.rotate_left(16) -} - -fn cmd_word(write: bool, incr: bool, func: u32, addr: u32, len: u32) -> u32 { - (write as u32) << 31 | (incr as u32) << 30 | (func & 0b11) << 28 | (addr & 0x1FFFF) << 11 | (len & 0x7FF) -} - -fn slice8_mut(x: &mut [u32]) -> &mut [u8] { - let len = x.len() * 4; - unsafe { slice::from_raw_parts_mut(x.as_mut_ptr() as _, len) } -} - -mod constants { - #![allow(unused)] - pub(crate) const FUNC_BUS: u32 = 0; - pub(crate) const FUNC_BACKPLANE: u32 = 1; - pub(crate) const FUNC_WLAN: u32 = 2; - pub(crate) const FUNC_BT: u32 = 3; - - pub(crate) const REG_BUS_CTRL: u32 = 0x0; - pub(crate) const REG_BUS_INTERRUPT: u32 = 0x04; // 16 bits - Interrupt status - pub(crate) const REG_BUS_INTERRUPT_ENABLE: u32 = 0x06; // 16 bits - Interrupt mask - pub(crate) const REG_BUS_STATUS: u32 = 0x8; - pub(crate) const REG_BUS_TEST_RO: u32 = 0x14; - pub(crate) const REG_BUS_TEST_RW: u32 = 0x18; - pub(crate) const REG_BUS_RESP_DELAY: u32 = 0x1c; - pub(crate) const WORD_LENGTH_32: u32 = 0x1; - pub(crate) const HIGH_SPEED: u32 = 0x10; - - // SPI_STATUS_REGISTER bits - pub(crate) const STATUS_DATA_NOT_AVAILABLE: u32 = 0x00000001; - pub(crate) const STATUS_UNDERFLOW: u32 = 0x00000002; - pub(crate) const STATUS_OVERFLOW: u32 = 0x00000004; - pub(crate) const STATUS_F2_INTR: u32 = 0x00000008; - pub(crate) const STATUS_F3_INTR: u32 = 0x00000010; - pub(crate) const STATUS_F2_RX_READY: u32 = 0x00000020; - pub(crate) const STATUS_F3_RX_READY: u32 = 0x00000040; - pub(crate) const STATUS_HOST_CMD_DATA_ERR: u32 = 0x00000080; - pub(crate) const STATUS_F2_PKT_AVAILABLE: u32 = 0x00000100; - pub(crate) const STATUS_F2_PKT_LEN_MASK: u32 = 0x000FFE00; - pub(crate) const STATUS_F2_PKT_LEN_SHIFT: u32 = 9; - pub(crate) const STATUS_F3_PKT_AVAILABLE: u32 = 0x00100000; - pub(crate) const STATUS_F3_PKT_LEN_MASK: u32 = 0xFFE00000; - pub(crate) const STATUS_F3_PKT_LEN_SHIFT: u32 = 21; - - pub(crate) const REG_BACKPLANE_GPIO_SELECT: u32 = 0x10005; - pub(crate) const REG_BACKPLANE_GPIO_OUTPUT: u32 = 0x10006; - pub(crate) const REG_BACKPLANE_GPIO_ENABLE: u32 = 0x10007; - pub(crate) const REG_BACKPLANE_FUNCTION2_WATERMARK: u32 = 0x10008; - pub(crate) const REG_BACKPLANE_DEVICE_CONTROL: u32 = 0x10009; - pub(crate) const REG_BACKPLANE_BACKPLANE_ADDRESS_LOW: u32 = 0x1000A; - pub(crate) const REG_BACKPLANE_BACKPLANE_ADDRESS_MID: u32 = 0x1000B; - pub(crate) const REG_BACKPLANE_BACKPLANE_ADDRESS_HIGH: u32 = 0x1000C; - pub(crate) const REG_BACKPLANE_FRAME_CONTROL: u32 = 0x1000D; - pub(crate) const REG_BACKPLANE_CHIP_CLOCK_CSR: u32 = 0x1000E; - pub(crate) const REG_BACKPLANE_PULL_UP: u32 = 0x1000F; - pub(crate) const REG_BACKPLANE_READ_FRAME_BC_LOW: u32 = 0x1001B; - pub(crate) const REG_BACKPLANE_READ_FRAME_BC_HIGH: u32 = 0x1001C; - pub(crate) const REG_BACKPLANE_WAKEUP_CTRL: u32 = 0x1001E; - pub(crate) const REG_BACKPLANE_SLEEP_CSR: u32 = 0x1001F; - - pub(crate) const BACKPLANE_WINDOW_SIZE: usize = 0x8000; - pub(crate) const BACKPLANE_ADDRESS_MASK: u32 = 0x7FFF; - pub(crate) const BACKPLANE_ADDRESS_32BIT_FLAG: u32 = 0x08000; - pub(crate) const BACKPLANE_MAX_TRANSFER_SIZE: usize = 64; - // Active Low Power (ALP) clock constants - pub(crate) const BACKPLANE_ALP_AVAIL_REQ: u8 = 0x08; - pub(crate) const BACKPLANE_ALP_AVAIL: u8 = 0x40; - - // Broadcom AMBA (Advanced Microcontroller Bus Architecture) Interconnect - // (AI) pub (crate) constants - pub(crate) const AI_IOCTRL_OFFSET: u32 = 0x408; - pub(crate) const AI_IOCTRL_BIT_FGC: u8 = 0x0002; - pub(crate) const AI_IOCTRL_BIT_CLOCK_EN: u8 = 0x0001; - pub(crate) const AI_IOCTRL_BIT_CPUHALT: u8 = 0x0020; - - pub(crate) const AI_RESETCTRL_OFFSET: u32 = 0x800; - pub(crate) const AI_RESETCTRL_BIT_RESET: u8 = 1; - - pub(crate) const AI_RESETSTATUS_OFFSET: u32 = 0x804; - - pub(crate) const TEST_PATTERN: u32 = 0x12345678; - pub(crate) const FEEDBEAD: u32 = 0xFEEDBEAD; - - // SPI_INTERRUPT_REGISTER and SPI_INTERRUPT_ENABLE_REGISTER Bits - pub(crate) const IRQ_DATA_UNAVAILABLE: u16 = 0x0001; // Requested data not available; Clear by writing a "1" - pub(crate) const IRQ_F2_F3_FIFO_RD_UNDERFLOW: u16 = 0x0002; - pub(crate) const IRQ_F2_F3_FIFO_WR_OVERFLOW: u16 = 0x0004; - pub(crate) const IRQ_COMMAND_ERROR: u16 = 0x0008; // Cleared by writing 1 - pub(crate) const IRQ_DATA_ERROR: u16 = 0x0010; // Cleared by writing 1 - pub(crate) const IRQ_F2_PACKET_AVAILABLE: u16 = 0x0020; - pub(crate) const IRQ_F3_PACKET_AVAILABLE: u16 = 0x0040; - pub(crate) const IRQ_F1_OVERFLOW: u16 = 0x0080; // Due to last write. Bkplane has pending write requests - pub(crate) const IRQ_MISC_INTR0: u16 = 0x0100; - pub(crate) const IRQ_MISC_INTR1: u16 = 0x0200; - pub(crate) const IRQ_MISC_INTR2: u16 = 0x0400; - pub(crate) const IRQ_MISC_INTR3: u16 = 0x0800; - pub(crate) const IRQ_MISC_INTR4: u16 = 0x1000; - pub(crate) const IRQ_F1_INTR: u16 = 0x2000; - pub(crate) const IRQ_F2_INTR: u16 = 0x4000; - pub(crate) const IRQ_F3_INTR: u16 = 0x8000; - - pub(crate) const IOCTL_CMD_UP: u32 = 2; - pub(crate) const IOCTL_CMD_SET_SSID: u32 = 26; - pub(crate) const IOCTL_CMD_ANTDIV: u32 = 64; - pub(crate) const IOCTL_CMD_SET_VAR: u32 = 263; - pub(crate) const IOCTL_CMD_GET_VAR: u32 = 262; - pub(crate) const IOCTL_CMD_SET_PASSPHRASE: u32 = 268; - - pub(crate) const CHANNEL_TYPE_CONTROL: u8 = 0; - pub(crate) const CHANNEL_TYPE_EVENT: u8 = 1; - pub(crate) const CHANNEL_TYPE_DATA: u8 = 2; - - // CYW_SPID command structure constants. - pub(crate) const WRITE: bool = true; - pub(crate) const READ: bool = false; - pub(crate) const INC_ADDR: bool = true; - pub(crate) const FIXED_ADDR: bool = false; -} -use crate::constants::*; +use crate::structs::*; #[derive(Clone, Copy)] pub enum IoctlType { @@ -565,15 +447,11 @@ impl<'a> embassy_net::Device for NetDevice<'a> { } pub struct Runner<'a, PWR, SPI> { + bus: Bus, + state: &'a State, - - pwr: PWR, - spi: SPI, - ioctl_id: u16, sdpcm_seq: u8, - backplane_window: u32, - sdpcm_seq_max: u8, #[cfg(feature = "firmware-logs")] @@ -600,14 +478,11 @@ where SPI::Bus: SpiBusRead + SpiBusWrite, { let mut runner = Runner { - state, - pwr, - spi, + bus: Bus::new(pwr, spi), + state, ioctl_id: 0, sdpcm_seq: 0, - backplane_window: 0xAAAA_AAAA, - sdpcm_seq_max: 1, #[cfg(feature = "firmware-logs")] @@ -631,62 +506,41 @@ where SPI::Bus: SpiBusRead + SpiBusWrite, { async fn init(&mut self, firmware: &[u8]) { - // Reset - self.pwr.set_low().unwrap(); - Timer::after(Duration::from_millis(20)).await; - self.pwr.set_high().unwrap(); - Timer::after(Duration::from_millis(250)).await; - - info!("waiting for ping..."); - while self.read32_swapped(REG_BUS_TEST_RO).await != FEEDBEAD {} - info!("ping ok"); - - self.write32_swapped(REG_BUS_TEST_RW, TEST_PATTERN).await; - let val = self.read32_swapped(REG_BUS_TEST_RW).await; - assert_eq!(val, TEST_PATTERN); - - // 32-bit word length, little endian (which is the default endianess). - self.write32_swapped(REG_BUS_CTRL, WORD_LENGTH_32 | HIGH_SPEED).await; - - let val = self.read32(FUNC_BUS, REG_BUS_TEST_RO).await; - assert_eq!(val, FEEDBEAD); - let val = self.read32(FUNC_BUS, REG_BUS_TEST_RW).await; - assert_eq!(val, TEST_PATTERN); - - // No response delay in any of the funcs. - // seems to break backplane??? eat the 4-byte delay instead, that's what the vendor drivers do... - //self.write32(FUNC_BUS, REG_BUS_RESP_DELAY, 0).await; + self.bus.init().await; // Init ALP (Active Low Power) clock - self.write8(FUNC_BACKPLANE, REG_BACKPLANE_CHIP_CLOCK_CSR, BACKPLANE_ALP_AVAIL_REQ) + self.bus + .write8(FUNC_BACKPLANE, REG_BACKPLANE_CHIP_CLOCK_CSR, BACKPLANE_ALP_AVAIL_REQ) .await; info!("waiting for clock..."); - while self.read8(FUNC_BACKPLANE, REG_BACKPLANE_CHIP_CLOCK_CSR).await & BACKPLANE_ALP_AVAIL == 0 {} + while self.bus.read8(FUNC_BACKPLANE, REG_BACKPLANE_CHIP_CLOCK_CSR).await & BACKPLANE_ALP_AVAIL == 0 {} info!("clock ok"); - let chip_id = self.bp_read16(0x1800_0000).await; + let chip_id = self.bus.bp_read16(0x1800_0000).await; info!("chip ID: {}", chip_id); // Upload firmware. self.core_disable(Core::WLAN).await; self.core_reset(Core::SOCSRAM).await; - self.bp_write32(CHIP.socsram_base_address + 0x10, 3).await; - self.bp_write32(CHIP.socsram_base_address + 0x44, 0).await; + self.bus.bp_write32(CHIP.socsram_base_address + 0x10, 3).await; + self.bus.bp_write32(CHIP.socsram_base_address + 0x44, 0).await; let ram_addr = CHIP.atcm_ram_base_address; info!("loading fw"); - self.bp_write(ram_addr, firmware).await; + self.bus.bp_write(ram_addr, firmware).await; info!("loading nvram"); // Round up to 4 bytes. let nvram_len = (NVRAM.len() + 3) / 4 * 4; - self.bp_write(ram_addr + CHIP.chip_ram_size - 4 - nvram_len as u32, NVRAM) + self.bus + .bp_write(ram_addr + CHIP.chip_ram_size - 4 - nvram_len as u32, NVRAM) .await; let nvram_len_words = nvram_len as u32 / 4; let nvram_len_magic = (!nvram_len_words << 16) | nvram_len_words; - self.bp_write32(ram_addr + CHIP.chip_ram_size - 4, nvram_len_magic) + self.bus + .bp_write32(ram_addr + CHIP.chip_ram_size - 4, nvram_len_magic) .await; // Start core! @@ -694,18 +548,20 @@ where self.core_reset(Core::WLAN).await; assert!(self.core_is_up(Core::WLAN).await); - while self.read8(FUNC_BACKPLANE, REG_BACKPLANE_CHIP_CLOCK_CSR).await & 0x80 == 0 {} + while self.bus.read8(FUNC_BACKPLANE, REG_BACKPLANE_CHIP_CLOCK_CSR).await & 0x80 == 0 {} // "Set up the interrupt mask and enable interrupts" - self.bp_write32(CHIP.sdiod_core_base_address + 0x24, 0xF0).await; + self.bus.bp_write32(CHIP.sdiod_core_base_address + 0x24, 0xF0).await; // "Lower F2 Watermark to avoid DMA Hang in F2 when SD Clock is stopped." // Sounds scary... - self.write8(FUNC_BACKPLANE, REG_BACKPLANE_FUNCTION2_WATERMARK, 32).await; + self.bus + .write8(FUNC_BACKPLANE, REG_BACKPLANE_FUNCTION2_WATERMARK, 32) + .await; // wait for wifi startup info!("waiting for wifi init..."); - while self.read32(FUNC_BUS, REG_BUS_STATUS).await & STATUS_F2_RX_READY == 0 {} + while self.bus.read32(FUNC_BUS, REG_BUS_STATUS).await & STATUS_F2_RX_READY == 0 {} // Some random configs related to sleep. // These aren't needed if we don't want to sleep the bus. @@ -713,25 +569,25 @@ where // being on the same pin as MOSI/MISO? /* - let mut val = self.read8(FUNC_BACKPLANE, REG_BACKPLANE_WAKEUP_CTRL).await; + let mut val = self.bus.read8(FUNC_BACKPLANE, REG_BACKPLANE_WAKEUP_CTRL).await; val |= 0x02; // WAKE_TILL_HT_AVAIL - self.write8(FUNC_BACKPLANE, REG_BACKPLANE_WAKEUP_CTRL, val).await; - self.write8(FUNC_BUS, 0xF0, 0x08).await; // SDIOD_CCCR_BRCM_CARDCAP.CMD_NODEC = 1 - self.write8(FUNC_BACKPLANE, REG_BACKPLANE_CHIP_CLOCK_CSR, 0x02).await; // SBSDIO_FORCE_HT + self.bus.write8(FUNC_BACKPLANE, REG_BACKPLANE_WAKEUP_CTRL, val).await; + self.bus.write8(FUNC_BUS, 0xF0, 0x08).await; // SDIOD_CCCR_BRCM_CARDCAP.CMD_NODEC = 1 + self.bus.write8(FUNC_BACKPLANE, REG_BACKPLANE_CHIP_CLOCK_CSR, 0x02).await; // SBSDIO_FORCE_HT - let mut val = self.read8(FUNC_BACKPLANE, REG_BACKPLANE_SLEEP_CSR).await; + let mut val = self.bus.read8(FUNC_BACKPLANE, REG_BACKPLANE_SLEEP_CSR).await; val |= 0x01; // SBSDIO_SLPCSR_KEEP_SDIO_ON - self.write8(FUNC_BACKPLANE, REG_BACKPLANE_SLEEP_CSR, val).await; + self.bus.write8(FUNC_BACKPLANE, REG_BACKPLANE_SLEEP_CSR, val).await; */ // clear pulls - self.write8(FUNC_BACKPLANE, REG_BACKPLANE_PULL_UP, 0).await; - let _ = self.read8(FUNC_BACKPLANE, REG_BACKPLANE_PULL_UP).await; + self.bus.write8(FUNC_BACKPLANE, REG_BACKPLANE_PULL_UP, 0).await; + let _ = self.bus.read8(FUNC_BACKPLANE, REG_BACKPLANE_PULL_UP).await; // start HT clock - //self.write8(FUNC_BACKPLANE, REG_BACKPLANE_CHIP_CLOCK_CSR, 0x10).await; + //self.bus.write8(FUNC_BACKPLANE, REG_BACKPLANE_CHIP_CLOCK_CSR, 0x10).await; //info!("waiting for HT clock..."); - //while self.read8(FUNC_BACKPLANE, REG_BACKPLANE_CHIP_CLOCK_CSR).await & 0x80 == 0 {} + //while self.bus.read8(FUNC_BACKPLANE, REG_BACKPLANE_CHIP_CLOCK_CSR).await & 0x80 == 0 {} //info!("clock ok"); #[cfg(feature = "firmware-logs")] @@ -744,13 +600,12 @@ where async fn log_init(&mut self) { // Initialize shared memory for logging. - let shared_addr = self - .bp_read32(CHIP.atcm_ram_base_address + CHIP.chip_ram_size - 4 - CHIP.socram_srmem_size) - .await; + let addr = CHIP.atcm_ram_base_address + CHIP.chip_ram_size - 4 - CHIP.socram_srmem_size; + let shared_addr = self.bus.bp_read32(addr).await; info!("shared_addr {:08x}", shared_addr); let mut shared = [0; SharedMemData::SIZE]; - self.bp_read(shared_addr, &mut shared).await; + self.bus.bp_read(shared_addr, &mut shared).await; let shared = SharedMemData::from_bytes(&shared); info!("shared: {:08x}", shared); @@ -761,7 +616,7 @@ where async fn log_read(&mut self) { // Read log struct let mut log = [0; SharedMemLog::SIZE]; - self.bp_read(self.log.addr, &mut log).await; + self.bus.bp_read(self.log.addr, &mut log).await; let log = SharedMemLog::from_bytes(&log); let idx = log.idx as usize; @@ -774,7 +629,7 @@ where // Read entire buf for now. We could read only what we need, but then we // run into annoying alignment issues in `bp_read`. let mut buf = [0; 0x400]; - self.bp_read(log.buf, &mut buf).await; + self.bus.bp_read(log.buf, &mut buf).await; while self.log.last_idx != idx as usize { let b = buf[self.log.last_idx]; @@ -821,29 +676,19 @@ where } // Receive stuff - let irq = self.read16(FUNC_BUS, REG_BUS_INTERRUPT).await; + let irq = self.bus.read16(FUNC_BUS, REG_BUS_INTERRUPT).await; if irq & IRQ_F2_PACKET_AVAILABLE != 0 { let mut status = 0xFFFF_FFFF; while status == 0xFFFF_FFFF { - status = self.read32(FUNC_BUS, REG_BUS_STATUS).await; + status = self.bus.read32(FUNC_BUS, REG_BUS_STATUS).await; } if status & STATUS_F2_PKT_AVAILABLE != 0 { let len = (status & STATUS_F2_PKT_LEN_MASK) >> STATUS_F2_PKT_LEN_SHIFT; - let cmd = cmd_word(READ, INC_ADDR, FUNC_WLAN, 0, len); - - transaction!(&mut self.spi, |bus| async { - bus.write(&[cmd]).await?; - bus.read(&mut buf[..(len as usize + 3) / 4]).await?; - Ok(()) - }) - .await - .unwrap(); - + self.bus.wlan_read(&mut buf[..(len as usize + 3) / 4]).await; trace!("rx {:02x}", &slice8_mut(&mut buf)[..(len as usize).min(48)]); - self.rx(&slice8_mut(&mut buf)[..len as usize]); } } @@ -893,14 +738,7 @@ where trace!(" {:02x}", &buf8[..total_len.min(48)]); - let cmd = cmd_word(WRITE, INC_ADDR, FUNC_WLAN, 0, total_len as _); - transaction!(&mut self.spi, |bus| async { - bus.write(&[cmd]).await?; - bus.write(&buf[..(total_len / 4)]).await?; - Ok(()) - }) - .await - .unwrap(); + self.bus.wlan_write(&buf[..(total_len / 4)]).await; } fn rx(&mut self, packet: &[u8]) { @@ -1086,52 +924,49 @@ where trace!(" {:02x}", &buf8[..total_len.min(48)]); - let cmd = cmd_word(WRITE, INC_ADDR, FUNC_WLAN, 0, total_len as _); - - transaction!(&mut self.spi, |bus| async { - bus.write(&[cmd]).await?; - bus.write(&buf[..total_len / 4]).await?; - Ok(()) - }) - .await - .unwrap(); + self.bus.wlan_write(&buf[..total_len / 4]).await; } async fn core_disable(&mut self, core: Core) { let base = core.base_addr(); // Dummy read? - let _ = self.bp_read8(base + AI_RESETCTRL_OFFSET).await; + let _ = self.bus.bp_read8(base + AI_RESETCTRL_OFFSET).await; // Check it isn't already reset - let r = self.bp_read8(base + AI_RESETCTRL_OFFSET).await; + let r = self.bus.bp_read8(base + AI_RESETCTRL_OFFSET).await; if r & AI_RESETCTRL_BIT_RESET != 0 { return; } - self.bp_write8(base + AI_IOCTRL_OFFSET, 0).await; - let _ = self.bp_read8(base + AI_IOCTRL_OFFSET).await; + self.bus.bp_write8(base + AI_IOCTRL_OFFSET, 0).await; + let _ = self.bus.bp_read8(base + AI_IOCTRL_OFFSET).await; block_for(Duration::from_millis(1)); - self.bp_write8(base + AI_RESETCTRL_OFFSET, AI_RESETCTRL_BIT_RESET).await; - let _ = self.bp_read8(base + AI_RESETCTRL_OFFSET).await; + self.bus + .bp_write8(base + AI_RESETCTRL_OFFSET, AI_RESETCTRL_BIT_RESET) + .await; + let _ = self.bus.bp_read8(base + AI_RESETCTRL_OFFSET).await; } async fn core_reset(&mut self, core: Core) { self.core_disable(core).await; let base = core.base_addr(); - self.bp_write8(base + AI_IOCTRL_OFFSET, AI_IOCTRL_BIT_FGC | AI_IOCTRL_BIT_CLOCK_EN) + self.bus + .bp_write8(base + AI_IOCTRL_OFFSET, AI_IOCTRL_BIT_FGC | AI_IOCTRL_BIT_CLOCK_EN) .await; - let _ = self.bp_read8(base + AI_IOCTRL_OFFSET).await; + let _ = self.bus.bp_read8(base + AI_IOCTRL_OFFSET).await; - self.bp_write8(base + AI_RESETCTRL_OFFSET, 0).await; + self.bus.bp_write8(base + AI_RESETCTRL_OFFSET, 0).await; Timer::after(Duration::from_millis(1)).await; - self.bp_write8(base + AI_IOCTRL_OFFSET, AI_IOCTRL_BIT_CLOCK_EN).await; - let _ = self.bp_read8(base + AI_IOCTRL_OFFSET).await; + self.bus + .bp_write8(base + AI_IOCTRL_OFFSET, AI_IOCTRL_BIT_CLOCK_EN) + .await; + let _ = self.bus.bp_read8(base + AI_IOCTRL_OFFSET).await; Timer::after(Duration::from_millis(1)).await; } @@ -1139,13 +974,13 @@ where async fn core_is_up(&mut self, core: Core) -> bool { let base = core.base_addr(); - let io = self.bp_read8(base + AI_IOCTRL_OFFSET).await; + let io = self.bus.bp_read8(base + AI_IOCTRL_OFFSET).await; if io & (AI_IOCTRL_BIT_FGC | AI_IOCTRL_BIT_CLOCK_EN) != AI_IOCTRL_BIT_CLOCK_EN { debug!("core_is_up: returning false due to bad ioctrl {:02x}", io); return false; } - let r = self.bp_read8(base + AI_RESETCTRL_OFFSET).await; + let r = self.bus.bp_read8(base + AI_RESETCTRL_OFFSET).await; if r & (AI_RESETCTRL_BIT_RESET) != 0 { debug!("core_is_up: returning false due to bad resetctrl {:02x}", r); return false; @@ -1153,242 +988,11 @@ where true } +} - #[allow(unused)] - async fn bp_read(&mut self, mut addr: u32, mut data: &mut [u8]) { - // It seems the HW force-aligns the addr - // to 2 if data.len() >= 2 - // to 4 if data.len() >= 4 - // To simplify, enforce 4-align for now. - assert!(addr % 4 == 0); - - let mut buf = [0u32; BACKPLANE_MAX_TRANSFER_SIZE / 4]; - - while !data.is_empty() { - // Ensure transfer doesn't cross a window boundary. - let window_offs = addr & BACKPLANE_ADDRESS_MASK; - let window_remaining = BACKPLANE_WINDOW_SIZE - window_offs as usize; - - let len = data.len().min(BACKPLANE_MAX_TRANSFER_SIZE).min(window_remaining); - - self.backplane_set_window(addr).await; - - let cmd = cmd_word(READ, INC_ADDR, FUNC_BACKPLANE, window_offs, len as u32); - - transaction!(&mut self.spi, |bus| async { - bus.write(&[cmd]).await?; - - // 4-byte response delay. - let mut junk = [0; 1]; - bus.read(&mut junk).await?; - - // Read data - bus.read(&mut buf[..(len + 3) / 4]).await?; - Ok(()) - }) - .await - .unwrap(); - - data[..len].copy_from_slice(&slice8_mut(&mut buf)[..len]); - - // Advance ptr. - addr += len as u32; - data = &mut data[len..]; - } - } - - async fn bp_write(&mut self, mut addr: u32, mut data: &[u8]) { - // It seems the HW force-aligns the addr - // to 2 if data.len() >= 2 - // to 4 if data.len() >= 4 - // To simplify, enforce 4-align for now. - assert!(addr % 4 == 0); - - let mut buf = [0u32; BACKPLANE_MAX_TRANSFER_SIZE / 4]; - - while !data.is_empty() { - // Ensure transfer doesn't cross a window boundary. - let window_offs = addr & BACKPLANE_ADDRESS_MASK; - let window_remaining = BACKPLANE_WINDOW_SIZE - window_offs as usize; - - let len = data.len().min(BACKPLANE_MAX_TRANSFER_SIZE).min(window_remaining); - slice8_mut(&mut buf)[..len].copy_from_slice(&data[..len]); - - self.backplane_set_window(addr).await; - - let cmd = cmd_word(WRITE, INC_ADDR, FUNC_BACKPLANE, window_offs, len as u32); - - transaction!(&mut self.spi, |bus| async { - bus.write(&[cmd]).await?; - bus.write(&buf[..(len + 3) / 4]).await?; - Ok(()) - }) - .await - .unwrap(); - - // Advance ptr. - addr += len as u32; - data = &data[len..]; - } - } - - async fn bp_read8(&mut self, addr: u32) -> u8 { - self.backplane_readn(addr, 1).await as u8 - } - - async fn bp_write8(&mut self, addr: u32, val: u8) { - self.backplane_writen(addr, val as u32, 1).await - } - - async fn bp_read16(&mut self, addr: u32) -> u16 { - self.backplane_readn(addr, 2).await as u16 - } - - #[allow(unused)] - async fn bp_write16(&mut self, addr: u32, val: u16) { - self.backplane_writen(addr, val as u32, 2).await - } - - #[allow(unused)] - async fn bp_read32(&mut self, addr: u32) -> u32 { - self.backplane_readn(addr, 4).await - } - - async fn bp_write32(&mut self, addr: u32, val: u32) { - self.backplane_writen(addr, val, 4).await - } - - async fn backplane_readn(&mut self, addr: u32, len: u32) -> u32 { - self.backplane_set_window(addr).await; - - let mut bus_addr = addr & BACKPLANE_ADDRESS_MASK; - if len == 4 { - bus_addr |= BACKPLANE_ADDRESS_32BIT_FLAG - } - self.readn(FUNC_BACKPLANE, bus_addr, len).await - } - - async fn backplane_writen(&mut self, addr: u32, val: u32, len: u32) { - self.backplane_set_window(addr).await; - - let mut bus_addr = addr & BACKPLANE_ADDRESS_MASK; - if len == 4 { - bus_addr |= BACKPLANE_ADDRESS_32BIT_FLAG - } - self.writen(FUNC_BACKPLANE, bus_addr, val, len).await - } - - async fn backplane_set_window(&mut self, addr: u32) { - let new_window = addr & !BACKPLANE_ADDRESS_MASK; - - if (new_window >> 24) as u8 != (self.backplane_window >> 24) as u8 { - self.write8( - FUNC_BACKPLANE, - REG_BACKPLANE_BACKPLANE_ADDRESS_HIGH, - (new_window >> 24) as u8, - ) - .await; - } - if (new_window >> 16) as u8 != (self.backplane_window >> 16) as u8 { - self.write8( - FUNC_BACKPLANE, - REG_BACKPLANE_BACKPLANE_ADDRESS_MID, - (new_window >> 16) as u8, - ) - .await; - } - if (new_window >> 8) as u8 != (self.backplane_window >> 8) as u8 { - self.write8( - FUNC_BACKPLANE, - REG_BACKPLANE_BACKPLANE_ADDRESS_LOW, - (new_window >> 8) as u8, - ) - .await; - } - self.backplane_window = new_window; - } - - async fn read8(&mut self, func: u32, addr: u32) -> u8 { - self.readn(func, addr, 1).await as u8 - } - - async fn write8(&mut self, func: u32, addr: u32, val: u8) { - self.writen(func, addr, val as u32, 1).await - } - - async fn read16(&mut self, func: u32, addr: u32) -> u16 { - self.readn(func, addr, 2).await as u16 - } - - #[allow(unused)] - async fn write16(&mut self, func: u32, addr: u32, val: u16) { - self.writen(func, addr, val as u32, 2).await - } - - async fn read32(&mut self, func: u32, addr: u32) -> u32 { - self.readn(func, addr, 4).await - } - - #[allow(unused)] - async fn write32(&mut self, func: u32, addr: u32, val: u32) { - self.writen(func, addr, val, 4).await - } - - async fn readn(&mut self, func: u32, addr: u32, len: u32) -> u32 { - let cmd = cmd_word(READ, INC_ADDR, func, addr, len); - let mut buf = [0; 1]; - - transaction!(&mut self.spi, |bus| async { - bus.write(&[cmd]).await?; - if func == FUNC_BACKPLANE { - // 4-byte response delay. - bus.read(&mut buf).await?; - } - bus.read(&mut buf).await?; - Ok(()) - }) - .await - .unwrap(); - - buf[0] - } - - async fn writen(&mut self, func: u32, addr: u32, val: u32, len: u32) { - let cmd = cmd_word(WRITE, INC_ADDR, func, addr, len); - - transaction!(&mut self.spi, |bus| async { - bus.write(&[cmd, val]).await?; - Ok(()) - }) - .await - .unwrap(); - } - - async fn read32_swapped(&mut self, addr: u32) -> u32 { - let cmd = cmd_word(READ, INC_ADDR, FUNC_BUS, addr, 4); - let mut buf = [0; 1]; - - transaction!(&mut self.spi, |bus| async { - bus.write(&[swap16(cmd)]).await?; - bus.read(&mut buf).await?; - Ok(()) - }) - .await - .unwrap(); - - swap16(buf[0]) - } - - async fn write32_swapped(&mut self, addr: u32, val: u32) { - let cmd = cmd_word(WRITE, INC_ADDR, FUNC_BUS, addr, 4); - - transaction!(&mut self.spi, |bus| async { - bus.write(&[swap16(cmd), swap16(val)]).await?; - Ok(()) - }) - .await - .unwrap(); - } +fn slice8_mut(x: &mut [u32]) -> &mut [u8] { + let len = x.len() * 4; + unsafe { slice::from_raw_parts_mut(x.as_mut_ptr() as _, len) } } macro_rules! nvram { From 2548bbdd65fc3094f624bd043a1a9a296f9184b5 Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Tue, 27 Dec 2022 01:19:26 +0100 Subject: [PATCH 055/178] Update Embassy. --- Cargo.toml | 2 +- examples/rpi-pico-w/Cargo.toml | 18 +-- examples/rpi-pico-w/src/main.rs | 6 +- src/lib.rs | 218 +++++++++++++------------------- 4 files changed, 101 insertions(+), 143 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index dadfb5c5..6e323744 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,7 +14,7 @@ firmware-logs = [] embassy-time = { version = "0.1.0" } embassy-sync = { version = "0.1.0" } embassy-futures = { version = "0.1.0" } -embassy-net = { version = "0.1.0" } +embassy-net-driver-channel = { version = "0.1.0" } atomic-polyfill = "0.1.5" defmt = { version = "0.3", optional = true } diff --git a/examples/rpi-pico-w/Cargo.toml b/examples/rpi-pico-w/Cargo.toml index b817289e..fa1cad8c 100644 --- a/examples/rpi-pico-w/Cargo.toml +++ b/examples/rpi-pico-w/Cargo.toml @@ -9,7 +9,7 @@ cyw43 = { path = "../../", features = ["defmt", "firmware-logs"]} embassy-executor = { version = "0.1.0", features = ["defmt", "integrated-timers"] } embassy-time = { version = "0.1.0", features = ["defmt", "defmt-timestamp-uptime"] } embassy-rp = { version = "0.1.0", features = ["defmt", "unstable-traits", "nightly", "unstable-pac", "time-driver"] } -embassy-net = { version = "0.1.0", features = ["defmt", "tcp", "dhcpv4", "medium-ethernet", "pool-16", "unstable-traits", "nightly"] } +embassy-net = { version = "0.1.0", features = ["defmt", "tcp", "dhcpv4", "medium-ethernet", "unstable-traits", "nightly"] } atomic-polyfill = "0.1.5" static_cell = "1.0" @@ -28,12 +28,14 @@ heapless = "0.7.15" [patch.crates-io] -embassy-executor = { git = "https://github.com/embassy-rs/embassy", rev = "645fb66a5122bdc8180e0e65d076ca103431a426" } -embassy-time = { git = "https://github.com/embassy-rs/embassy", rev = "645fb66a5122bdc8180e0e65d076ca103431a426" } -embassy-futures = { git = "https://github.com/embassy-rs/embassy", rev = "645fb66a5122bdc8180e0e65d076ca103431a426" } -embassy-sync = { git = "https://github.com/embassy-rs/embassy", rev = "645fb66a5122bdc8180e0e65d076ca103431a426" } -embassy-rp = { git = "https://github.com/embassy-rs/embassy", rev = "645fb66a5122bdc8180e0e65d076ca103431a426" } -embassy-net = { git = "https://github.com/embassy-rs/embassy", rev = "645fb66a5122bdc8180e0e65d076ca103431a426" } +embassy-executor = { git = "https://github.com/embassy-rs/embassy", rev = "771806be790a2758f1314d6460defe7c2f0d3e99" } +embassy-time = { git = "https://github.com/embassy-rs/embassy", rev = "771806be790a2758f1314d6460defe7c2f0d3e99" } +embassy-futures = { git = "https://github.com/embassy-rs/embassy", rev = "771806be790a2758f1314d6460defe7c2f0d3e99" } +embassy-sync = { git = "https://github.com/embassy-rs/embassy", rev = "771806be790a2758f1314d6460defe7c2f0d3e99" } +embassy-rp = { git = "https://github.com/embassy-rs/embassy", rev = "771806be790a2758f1314d6460defe7c2f0d3e99" } +embassy-net = { git = "https://github.com/embassy-rs/embassy", rev = "771806be790a2758f1314d6460defe7c2f0d3e99" } +embassy-net-driver = { git = "https://github.com/embassy-rs/embassy", rev = "771806be790a2758f1314d6460defe7c2f0d3e99" } +embassy-net-driver-channel = { git = "https://github.com/embassy-rs/embassy", rev = "771806be790a2758f1314d6460defe7c2f0d3e99" } [profile.dev] debug = 2 @@ -43,7 +45,7 @@ overflow-checks = true [profile.release] codegen-units = 1 -debug = 2 +debug = 1 debug-assertions = false incremental = false lto = 'fat' diff --git a/examples/rpi-pico-w/src/main.rs b/examples/rpi-pico-w/src/main.rs index a19f3859..fd58e46d 100644 --- a/examples/rpi-pico-w/src/main.rs +++ b/examples/rpi-pico-w/src/main.rs @@ -34,7 +34,7 @@ async fn wifi_task( } #[embassy_executor::task] -async fn net_task(stack: &'static Stack>) -> ! { +async fn net_task(stack: &'static Stack>) -> ! { stack.run().await } @@ -66,11 +66,11 @@ async fn main(spawner: Spawner) { let spi = ExclusiveDevice::new(bus, cs); let state = singleton!(cyw43::State::new()); - let (mut control, runner) = cyw43::new(state, pwr, spi, fw).await; + let (net_device, mut control, runner) = cyw43::new(state, pwr, spi, fw).await; spawner.spawn(wifi_task(runner)).unwrap(); - let net_device = control.init(clm).await; + control.init(clm).await; //control.join_open(env!("WIFI_NETWORK")).await; control.join_wpa2(env!("WIFI_NETWORK"), env!("WIFI_PASSWORD")).await; diff --git a/src/lib.rs b/src/lib.rs index fa73b32e..25e6f8f1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -15,14 +15,10 @@ mod structs; use core::cell::Cell; use core::cmp::{max, min}; use core::slice; -use core::sync::atomic::Ordering; -use core::task::Waker; -use atomic_polyfill::AtomicBool; +use ch::driver::LinkState; use embassy_futures::yield_now; -use embassy_net::{PacketBoxExt, PacketBuf}; -use embassy_sync::blocking_mutex::raw::NoopRawMutex; -use embassy_sync::channel::Channel; +use embassy_net_driver_channel as ch; use embassy_time::{block_for, Duration, Timer}; use embedded_hal_1::digital::OutputPin; use embedded_hal_async::spi::{SpiBusRead, SpiBusWrite, SpiDevice}; @@ -32,6 +28,8 @@ use crate::consts::*; use crate::events::Event; use crate::structs::*; +const MTU: usize = 1514; + #[derive(Clone, Copy)] pub enum IoctlType { Get = 0, @@ -128,30 +126,25 @@ enum IoctlState { pub struct State { ioctl_state: Cell, - - tx_channel: Channel, - rx_channel: Channel, - link_up: AtomicBool, + ch: ch::State, } impl State { pub fn new() -> Self { Self { ioctl_state: Cell::new(IoctlState::Idle), - - tx_channel: Channel::new(), - rx_channel: Channel::new(), - link_up: AtomicBool::new(true), // TODO set up/down as we join/deassociate + ch: ch::State::new(), } } } pub struct Control<'a> { - state: &'a State, + state_ch: ch::StateRunner<'a>, + ioctl_state: &'a Cell, } impl<'a> Control<'a> { - pub async fn init(&mut self, clm: &[u8]) -> NetDevice<'a> { + pub async fn init(&mut self, clm: &[u8]) { const CHUNK_SIZE: usize = 1024; info!("Downloading CLM..."); @@ -258,12 +251,10 @@ impl<'a> Control<'a> { Timer::after(Duration::from_millis(100)).await; - info!("INIT DONE"); + self.state_ch.set_ethernet_address(mac_addr); + self.state_ch.set_link_state(LinkState::Up); // TODO do on join/leave - NetDevice { - state: self.state, - mac_addr, - } + info!("INIT DONE"); } pub async fn join_open(&mut self, ssid: &str) { @@ -381,75 +372,30 @@ impl<'a> Control<'a> { async fn ioctl(&mut self, kind: IoctlType, cmd: u32, iface: u32, buf: &mut [u8]) -> usize { // TODO cancel ioctl on future drop. - while !matches!(self.state.ioctl_state.get(), IoctlState::Idle) { + while !matches!(self.ioctl_state.get(), IoctlState::Idle) { yield_now().await; } - self.state - .ioctl_state - .set(IoctlState::Pending { kind, cmd, iface, buf }); + self.ioctl_state.set(IoctlState::Pending { kind, cmd, iface, buf }); let resp_len = loop { - if let IoctlState::Done { resp_len } = self.state.ioctl_state.get() { + if let IoctlState::Done { resp_len } = self.ioctl_state.get() { break resp_len; } yield_now().await; }; - self.state.ioctl_state.set(IoctlState::Idle); + self.ioctl_state.set(IoctlState::Idle); resp_len } } -pub struct NetDevice<'a> { - state: &'a State, - mac_addr: [u8; 6], -} - -impl<'a> embassy_net::Device for NetDevice<'a> { - fn register_waker(&mut self, waker: &Waker) { - // loopy loopy wakey wakey - waker.wake_by_ref() - } - - fn link_state(&mut self) -> embassy_net::LinkState { - match self.state.link_up.load(Ordering::Relaxed) { - true => embassy_net::LinkState::Up, - false => embassy_net::LinkState::Down, - } - } - - fn capabilities(&self) -> embassy_net::DeviceCapabilities { - let mut caps = embassy_net::DeviceCapabilities::default(); - caps.max_transmission_unit = 1514; // 1500 IP + 14 ethernet header - caps.medium = embassy_net::Medium::Ethernet; - caps - } - - fn is_transmit_ready(&mut self) -> bool { - true - } - - fn transmit(&mut self, pkt: PacketBuf) { - if self.state.tx_channel.try_send(pkt).is_err() { - warn!("TX failed") - } - } - - fn receive(&mut self) -> Option { - self.state.rx_channel.try_recv().ok() - } - - fn ethernet_address(&self) -> [u8; 6] { - self.mac_addr - } -} - pub struct Runner<'a, PWR, SPI> { + ch: ch::Runner<'a, MTU>, bus: Bus, - state: &'a State, + ioctl_state: &'a Cell, ioctl_id: u16, sdpcm_seq: u8, sdpcm_seq_max: u8, @@ -466,21 +412,27 @@ struct LogState { buf_count: usize, } +pub type NetDriver<'a> = ch::Device<'a, MTU>; + pub async fn new<'a, PWR, SPI>( - state: &'a State, + state: &'a mut State, pwr: PWR, spi: SPI, firmware: &[u8], -) -> (Control<'a>, Runner<'a, PWR, SPI>) +) -> (NetDriver<'a>, Control<'a>, Runner<'a, PWR, SPI>) where PWR: OutputPin, SPI: SpiDevice, SPI::Bus: SpiBusRead + SpiBusWrite, { + 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, bus: Bus::new(pwr, spi), - state, + ioctl_state: &state.ioctl_state, ioctl_id: 0, sdpcm_seq: 0, sdpcm_seq_max: 1, @@ -496,7 +448,14 @@ where runner.init(firmware).await; - (Control { state }, runner) + ( + device, + Control { + state_ch, + ioctl_state: &state.ioctl_state, + }, + runner, + ) } impl<'a, PWR, SPI> Runner<'a, PWR, SPI> @@ -662,15 +621,55 @@ where if !self.has_credit() { warn!("TX stalled"); } else { - if let IoctlState::Pending { kind, cmd, iface, buf } = self.state.ioctl_state.get() { + if let IoctlState::Pending { kind, cmd, iface, buf } = self.ioctl_state.get() { self.send_ioctl(kind, cmd, iface, unsafe { &*buf }).await; - self.state.ioctl_state.set(IoctlState::Sent { buf }); + self.ioctl_state.set(IoctlState::Sent { buf }); } if !self.has_credit() { warn!("TX stalled"); } else { - if let Ok(p) = self.state.tx_channel.try_recv() { - self.send_packet(&p).await; + if let Some(packet) = self.ch.try_tx_buf() { + trace!("tx pkt {:02x}", &packet[..packet.len().min(48)]); + + let mut buf = [0; 512]; + let buf8 = slice8_mut(&mut buf); + + let total_len = SdpcmHeader::SIZE + BcdHeader::SIZE + packet.len(); + + let seq = self.sdpcm_seq; + self.sdpcm_seq = self.sdpcm_seq.wrapping_add(1); + + let sdpcm_header = SdpcmHeader { + len: total_len as u16, // TODO does this len need to be rounded up to u32? + len_inv: !total_len as u16, + sequence: seq, + channel_and_flags: CHANNEL_TYPE_DATA, + next_length: 0, + header_length: SdpcmHeader::SIZE as _, + wireless_flow_control: 0, + bus_data_credit: 0, + reserved: [0, 0], + }; + + let bcd_header = BcdHeader { + flags: BDC_VERSION << BDC_VERSION_SHIFT, + priority: 0, + flags2: 0, + data_offset: 0, + }; + trace!("tx {:?}", sdpcm_header); + trace!(" {:?}", bcd_header); + + buf8[0..SdpcmHeader::SIZE].copy_from_slice(&sdpcm_header.to_bytes()); + buf8[SdpcmHeader::SIZE..][..BcdHeader::SIZE].copy_from_slice(&bcd_header.to_bytes()); + buf8[SdpcmHeader::SIZE + BcdHeader::SIZE..][..packet.len()].copy_from_slice(packet); + + let total_len = (total_len + 3) & !3; // round up to 4byte + + trace!(" {:02x}", &buf8[..total_len.min(48)]); + + self.bus.wlan_write(&buf[..(total_len / 4)]).await; + self.ch.tx_done(); } } } @@ -686,7 +685,6 @@ where if status & STATUS_F2_PKT_AVAILABLE != 0 { let len = (status & STATUS_F2_PKT_LEN_MASK) >> STATUS_F2_PKT_LEN_SHIFT; - self.bus.wlan_read(&mut buf[..(len as usize + 3) / 4]).await; trace!("rx {:02x}", &slice8_mut(&mut buf)[..(len as usize).min(48)]); self.rx(&slice8_mut(&mut buf)[..len as usize]); @@ -698,49 +696,6 @@ where } } - async fn send_packet(&mut self, packet: &[u8]) { - trace!("tx pkt {:02x}", &packet[..packet.len().min(48)]); - - let mut buf = [0; 512]; - let buf8 = slice8_mut(&mut buf); - - let total_len = SdpcmHeader::SIZE + BcdHeader::SIZE + packet.len(); - - let seq = self.sdpcm_seq; - self.sdpcm_seq = self.sdpcm_seq.wrapping_add(1); - - let sdpcm_header = SdpcmHeader { - len: total_len as u16, // TODO does this len need to be rounded up to u32? - len_inv: !total_len as u16, - sequence: seq, - channel_and_flags: CHANNEL_TYPE_DATA, - next_length: 0, - header_length: SdpcmHeader::SIZE as _, - wireless_flow_control: 0, - bus_data_credit: 0, - reserved: [0, 0], - }; - - let bcd_header = BcdHeader { - flags: BDC_VERSION << BDC_VERSION_SHIFT, - priority: 0, - flags2: 0, - data_offset: 0, - }; - trace!("tx {:?}", sdpcm_header); - trace!(" {:?}", bcd_header); - - buf8[0..SdpcmHeader::SIZE].copy_from_slice(&sdpcm_header.to_bytes()); - buf8[SdpcmHeader::SIZE..][..BcdHeader::SIZE].copy_from_slice(&bcd_header.to_bytes()); - buf8[SdpcmHeader::SIZE + BcdHeader::SIZE..][..packet.len()].copy_from_slice(packet); - - let total_len = (total_len + 3) & !3; // round up to 4byte - - trace!(" {:02x}", &buf8[..total_len.min(48)]); - - self.bus.wlan_write(&buf[..(total_len / 4)]).await; - } - fn rx(&mut self, packet: &[u8]) { if packet.len() < SdpcmHeader::SIZE { warn!("packet too short, len={}", packet.len()); @@ -775,7 +730,7 @@ where let cdc_header = CdcHeader::from_bytes(payload[..CdcHeader::SIZE].try_into().unwrap()); trace!(" {:?}", cdc_header); - if let IoctlState::Sent { buf } = self.state.ioctl_state.get() { + if let IoctlState::Sent { buf } = self.ioctl_state.get() { if cdc_header.id == self.ioctl_id { if cdc_header.status != 0 { // TODO: propagate error instead @@ -786,7 +741,7 @@ where info!("IOCTL Response: {:02x}", &payload[CdcHeader::SIZE..][..resp_len]); (unsafe { &mut *buf }[..resp_len]).copy_from_slice(&payload[CdcHeader::SIZE..][..resp_len]); - self.state.ioctl_state.set(IoctlState::Done { resp_len }); + self.ioctl_state.set(IoctlState::Done { resp_len }); } } } @@ -859,11 +814,12 @@ where let packet = &payload[packet_start..]; trace!("rx pkt {:02x}", &packet[..(packet.len() as usize).min(48)]); - let mut p = unwrap!(embassy_net::PacketBox::new(embassy_net::Packet::new())); - p[..packet.len()].copy_from_slice(packet); - - if let Err(_) = self.state.rx_channel.try_send(p.slice(0..packet.len())) { - warn!("failed to push rxd packet to the channel.") + match self.ch.try_rx_buf() { + Some(buf) => { + buf[..packet.len()].copy_from_slice(packet); + self.ch.rx_done(packet.len()) + } + None => warn!("failed to push rxd packet to the channel."), } } _ => {} From 871700f05dbd30aac71d6a3b5446e7743a18b90b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mattias=20Gr=C3=B6nlund?= Date: Sat, 31 Dec 2022 16:25:37 +0100 Subject: [PATCH 056/178] Fixed length for wlan_read. The length provided in command word for FUNC_WLAN READ, should describe the actual bytes requested, not the size of the buffer which is sized in u32. --- src/bus.rs | 7 ++++--- src/lib.rs | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/bus.rs b/src/bus.rs index f220cffc..f64c0abb 100644 --- a/src/bus.rs +++ b/src/bus.rs @@ -48,11 +48,12 @@ where assert_eq!(val, TEST_PATTERN); } - pub async fn wlan_read(&mut self, buf: &mut [u32]) { - let cmd = cmd_word(READ, INC_ADDR, FUNC_WLAN, 0, buf.len() as u32 * 4); + pub async fn wlan_read(&mut self, buf: &mut [u32], len_in_u8: u32) { + let cmd = cmd_word(READ, INC_ADDR, FUNC_WLAN, 0, len_in_u8); + let len_in_u32 = (len_in_u8 as usize + 3) / 4; transaction!(&mut self.spi, |bus| async { bus.write(&[cmd]).await?; - bus.read(buf).await?; + bus.read(&mut buf[..len_in_u32]).await?; Ok(()) }) .await diff --git a/src/lib.rs b/src/lib.rs index fa73b32e..a606d6be 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -687,7 +687,7 @@ where if status & STATUS_F2_PKT_AVAILABLE != 0 { let len = (status & STATUS_F2_PKT_LEN_MASK) >> STATUS_F2_PKT_LEN_SHIFT; - self.bus.wlan_read(&mut buf[..(len as usize + 3) / 4]).await; + self.bus.wlan_read(&mut buf, len).await; trace!("rx {:02x}", &slice8_mut(&mut buf)[..(len as usize).min(48)]); self.rx(&slice8_mut(&mut buf)[..len as usize]); } From 001610f0d0b94859b8c8800dcdfa255343f2ea05 Mon Sep 17 00:00:00 2001 From: Lukas Krejci Date: Wed, 30 Nov 2022 15:57:52 +0100 Subject: [PATCH 057/178] Be able to specify the power management mode at init time. --- examples/rpi-pico-w/src/main.rs | 2 +- src/lib.rs | 106 +++++++++++++++++++++++++++++--- 2 files changed, 100 insertions(+), 8 deletions(-) diff --git a/examples/rpi-pico-w/src/main.rs b/examples/rpi-pico-w/src/main.rs index fd58e46d..73cfdf42 100644 --- a/examples/rpi-pico-w/src/main.rs +++ b/examples/rpi-pico-w/src/main.rs @@ -70,7 +70,7 @@ async fn main(spawner: Spawner) { spawner.spawn(wifi_task(runner)).unwrap(); - control.init(clm).await; + control.init(clm, cyw43::PowerManagementMode::PowerSave).await; //control.join_open(env!("WIFI_NETWORK")).await; control.join_wpa2(env!("WIFI_NETWORK"), env!("WIFI_PASSWORD")).await; diff --git a/src/lib.rs b/src/lib.rs index 94032271..884cb082 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -143,8 +143,92 @@ pub struct Control<'a> { ioctl_state: &'a Cell, } +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum PowerManagementMode { + /// Custom, officially unsupported mode. Use at your own risk. + /// All power-saving features set to their max at only a marginal decrease in power consumption + /// as oppposed to `Aggressive`. + SuperSave, + + /// Aggressive power saving mode. + Aggressive, + + /// The default mode. + PowerSave, + + /// Performance is prefered over power consumption but still some power is conserved as opposed to + /// `None`. + Performance, + + /// Unlike all the other PM modes, this lowers the power consumption at all times at the cost of + /// a much lower throughput. + ThroughputThrottling, + + /// No power management is configured. This consumes the most power. + None, +} + +impl Default for PowerManagementMode { + fn default() -> Self { + Self::PowerSave + } +} + +impl PowerManagementMode { + fn sleep_ret_ms(&self) -> u16 { + match self { + PowerManagementMode::SuperSave => 2000, + PowerManagementMode::Aggressive => 2000, + PowerManagementMode::PowerSave => 200, + PowerManagementMode::Performance => 20, + PowerManagementMode::ThroughputThrottling => 0, // value doesn't matter + PowerManagementMode::None => 0, // value doesn't matter + } + } + + fn beacon_period(&self) -> u8 { + match self { + PowerManagementMode::SuperSave => 255, + PowerManagementMode::Aggressive => 1, + PowerManagementMode::PowerSave => 1, + PowerManagementMode::Performance => 1, + PowerManagementMode::ThroughputThrottling => 0, // value doesn't matter + PowerManagementMode::None => 0, // value doesn't matter + } + } + + fn dtim_period(&self) -> u8 { + match self { + PowerManagementMode::SuperSave => 255, + PowerManagementMode::Aggressive => 1, + PowerManagementMode::PowerSave => 1, + PowerManagementMode::Performance => 1, + PowerManagementMode::ThroughputThrottling => 0, // value doesn't matter + PowerManagementMode::None => 0, // value doesn't matter + } + } + + fn assoc(&self) -> u8 { + match self { + PowerManagementMode::SuperSave => 255, + PowerManagementMode::Aggressive => 10, + PowerManagementMode::PowerSave => 10, + PowerManagementMode::Performance => 1, + PowerManagementMode::ThroughputThrottling => 0, // value doesn't matter + PowerManagementMode::None => 0, // value doesn't matter + } + } + + fn mode(&self) -> u32 { + match self { + PowerManagementMode::ThroughputThrottling => 1, + _ => 2, + } + } +} + impl<'a> Control<'a> { - pub async fn init(&mut self, clm: &[u8]) { + pub async fn init(&mut self, clm: &[u8], power_save_mode: PowerManagementMode) { const CHUNK_SIZE: usize = 1024; info!("Downloading CLM..."); @@ -239,12 +323,20 @@ impl<'a> Control<'a> { Timer::after(Duration::from_millis(100)).await; - // power save mode 2 - self.set_iovar_u32("pm2_sleep_ret", 0xc8).await; - self.set_iovar_u32("bcn_li_bcn", 1).await; - self.set_iovar_u32("bcn_li_dtim", 1).await; - self.set_iovar_u32("assoc_listen", 10).await; - self.ioctl_set_u32(0x86, 0, 2).await; + // power save mode + if power_save_mode != PowerManagementMode::None { + let mode = power_save_mode.mode(); + if mode == 2 { + self.set_iovar_u32("pm2_sleep_ret", power_save_mode.sleep_ret_ms() as u32) + .await; + self.set_iovar_u32("bcn_li_bcn", power_save_mode.beacon_period() as u32) + .await; + self.set_iovar_u32("bcn_li_dtim", power_save_mode.dtim_period() as u32) + .await; + self.set_iovar_u32("assoc_listen", power_save_mode.assoc() as u32).await; + } + self.ioctl_set_u32(86, 0, mode).await; + } self.ioctl_set_u32(110, 0, 1).await; // SET_GMODE = auto self.ioctl_set_u32(142, 0, 0).await; // SET_BAND = any From a2bae33d8460eee6c3af6f20a790f725cf2c5602 Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Mon, 2 Jan 2023 21:36:17 +0100 Subject: [PATCH 058/178] Add separate function to set power management mode. --- examples/rpi-pico-w/src/main.rs | 5 ++++- src/lib.rs | 29 +++++++++++++---------------- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/examples/rpi-pico-w/src/main.rs b/examples/rpi-pico-w/src/main.rs index 73cfdf42..d2f47fd6 100644 --- a/examples/rpi-pico-w/src/main.rs +++ b/examples/rpi-pico-w/src/main.rs @@ -70,7 +70,10 @@ async fn main(spawner: Spawner) { spawner.spawn(wifi_task(runner)).unwrap(); - control.init(clm, cyw43::PowerManagementMode::PowerSave).await; + control.init(clm).await; + control + .set_power_management(cyw43::PowerManagementMode::PowerSave) + .await; //control.join_open(env!("WIFI_NETWORK")).await; control.join_wpa2(env!("WIFI_NETWORK"), env!("WIFI_PASSWORD")).await; diff --git a/src/lib.rs b/src/lib.rs index 884cb082..5733506a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -228,7 +228,7 @@ impl PowerManagementMode { } impl<'a> Control<'a> { - pub async fn init(&mut self, clm: &[u8], power_save_mode: PowerManagementMode) { + pub async fn init(&mut self, clm: &[u8]) { const CHUNK_SIZE: usize = 1024; info!("Downloading CLM..."); @@ -323,21 +323,6 @@ impl<'a> Control<'a> { Timer::after(Duration::from_millis(100)).await; - // power save mode - if power_save_mode != PowerManagementMode::None { - let mode = power_save_mode.mode(); - if mode == 2 { - self.set_iovar_u32("pm2_sleep_ret", power_save_mode.sleep_ret_ms() as u32) - .await; - self.set_iovar_u32("bcn_li_bcn", power_save_mode.beacon_period() as u32) - .await; - self.set_iovar_u32("bcn_li_dtim", power_save_mode.dtim_period() as u32) - .await; - self.set_iovar_u32("assoc_listen", power_save_mode.assoc() as u32).await; - } - self.ioctl_set_u32(86, 0, mode).await; - } - self.ioctl_set_u32(110, 0, 1).await; // SET_GMODE = auto self.ioctl_set_u32(142, 0, 0).await; // SET_BAND = any @@ -349,6 +334,18 @@ impl<'a> Control<'a> { info!("INIT DONE"); } + pub async fn set_power_management(&mut self, mode: PowerManagementMode) { + // power save mode + let mode_num = mode.mode(); + if mode_num == 2 { + self.set_iovar_u32("pm2_sleep_ret", mode.sleep_ret_ms() as u32).await; + self.set_iovar_u32("bcn_li_bcn", mode.beacon_period() as u32).await; + self.set_iovar_u32("bcn_li_dtim", mode.dtim_period() as u32).await; + self.set_iovar_u32("assoc_listen", mode.assoc() as u32).await; + } + self.ioctl_set_u32(86, 0, mode_num).await; + } + pub async fn join_open(&mut self, ssid: &str) { self.set_iovar_u32("ampdu_ba_wsize", 8).await; From 0bcd1b1e10e0edefa520ba3f293d34367b416c99 Mon Sep 17 00:00:00 2001 From: Aaron Tsui Date: Wed, 15 Feb 2023 11:08:27 +0800 Subject: [PATCH 059/178] update embassy dependences --- examples/rpi-pico-w/Cargo.toml | 16 ++++++++-------- examples/rpi-pico-w/src/main.rs | 8 ++++---- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/examples/rpi-pico-w/Cargo.toml b/examples/rpi-pico-w/Cargo.toml index fa1cad8c..99b82ca3 100644 --- a/examples/rpi-pico-w/Cargo.toml +++ b/examples/rpi-pico-w/Cargo.toml @@ -28,14 +28,14 @@ heapless = "0.7.15" [patch.crates-io] -embassy-executor = { git = "https://github.com/embassy-rs/embassy", rev = "771806be790a2758f1314d6460defe7c2f0d3e99" } -embassy-time = { git = "https://github.com/embassy-rs/embassy", rev = "771806be790a2758f1314d6460defe7c2f0d3e99" } -embassy-futures = { git = "https://github.com/embassy-rs/embassy", rev = "771806be790a2758f1314d6460defe7c2f0d3e99" } -embassy-sync = { git = "https://github.com/embassy-rs/embassy", rev = "771806be790a2758f1314d6460defe7c2f0d3e99" } -embassy-rp = { git = "https://github.com/embassy-rs/embassy", rev = "771806be790a2758f1314d6460defe7c2f0d3e99" } -embassy-net = { git = "https://github.com/embassy-rs/embassy", rev = "771806be790a2758f1314d6460defe7c2f0d3e99" } -embassy-net-driver = { git = "https://github.com/embassy-rs/embassy", rev = "771806be790a2758f1314d6460defe7c2f0d3e99" } -embassy-net-driver-channel = { git = "https://github.com/embassy-rs/embassy", rev = "771806be790a2758f1314d6460defe7c2f0d3e99" } +embassy-executor = { git = "https://github.com/embassy-rs/embassy", rev = "e3f8020c3bdf726dfa451b5b190f27191507a18f" } +embassy-time = { git = "https://github.com/embassy-rs/embassy", rev = "e3f8020c3bdf726dfa451b5b190f27191507a18f" } +embassy-futures = { git = "https://github.com/embassy-rs/embassy", rev = "e3f8020c3bdf726dfa451b5b190f27191507a18f" } +embassy-sync = { git = "https://github.com/embassy-rs/embassy", rev = "e3f8020c3bdf726dfa451b5b190f27191507a18f" } +embassy-rp = { git = "https://github.com/embassy-rs/embassy", rev = "e3f8020c3bdf726dfa451b5b190f27191507a18f" } +embassy-net = { git = "https://github.com/embassy-rs/embassy", rev = "e3f8020c3bdf726dfa451b5b190f27191507a18f" } +embassy-net-driver = { git = "https://github.com/embassy-rs/embassy", rev = "e3f8020c3bdf726dfa451b5b190f27191507a18f" } +embassy-net-driver-channel = { git = "https://github.com/embassy-rs/embassy", rev = "e3f8020c3bdf726dfa451b5b190f27191507a18f" } [profile.dev] debug = 2 diff --git a/examples/rpi-pico-w/src/main.rs b/examples/rpi-pico-w/src/main.rs index d2f47fd6..71459a12 100644 --- a/examples/rpi-pico-w/src/main.rs +++ b/examples/rpi-pico-w/src/main.rs @@ -9,7 +9,7 @@ use core::convert::Infallible; use defmt::*; use embassy_executor::Spawner; use embassy_net::tcp::TcpSocket; -use embassy_net::{Stack, StackResources}; +use embassy_net::{Config, Stack, StackResources}; use embassy_rp::gpio::{Flex, Level, Output}; use embassy_rp::peripherals::{PIN_23, PIN_24, PIN_25, PIN_29}; use embedded_hal_1::spi::ErrorType; @@ -78,8 +78,8 @@ async fn main(spawner: Spawner) { //control.join_open(env!("WIFI_NETWORK")).await; control.join_wpa2(env!("WIFI_NETWORK"), env!("WIFI_PASSWORD")).await; - let config = embassy_net::ConfigStrategy::Dhcp; - //let config = embassy_net::ConfigStrategy::Static(embassy_net::Config { + let config = Config::Dhcp(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)), @@ -92,7 +92,7 @@ async fn main(spawner: Spawner) { let stack = &*singleton!(Stack::new( net_device, config, - singleton!(StackResources::<1, 2, 8>::new()), + singleton!(StackResources::<2>::new()), seed )); From f34829f534297dfccb1c5b206bffcc7700ef86ae Mon Sep 17 00:00:00 2001 From: Pol Fernandez Date: Mon, 20 Feb 2023 21:03:39 +0100 Subject: [PATCH 060/178] Add stringify function --- examples/rpi-pico-w/src/main.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/examples/rpi-pico-w/src/main.rs b/examples/rpi-pico-w/src/main.rs index 71459a12..e71c2234 100644 --- a/examples/rpi-pico-w/src/main.rs +++ b/examples/rpi-pico-w/src/main.rs @@ -18,6 +18,9 @@ use embedded_io::asynch::Write; use static_cell::StaticCell; use {defmt_rtt as _, panic_probe as _}; +use heapless::String; + + macro_rules! singleton { ($val:expr) => {{ type T = impl Sized; @@ -129,7 +132,8 @@ async fn main(spawner: Spawner) { } }; - info!("rxd {:02x}", &buf[..n]); + info!("rxd {}", asciify(&buf[..n])); + match socket.write_all(&buf[..n]).await { Ok(()) => {} @@ -214,3 +218,7 @@ impl SpiBusWrite for MySpi { Ok(()) } } + +fn asciify(buf: &[u8],) -> String<4096> { + buf.into_iter().map(|c| *c as char).into_iter().collect() +} From f6f041b05d9702982e3cf56bb76f7904485677c8 Mon Sep 17 00:00:00 2001 From: Pol Fernandez Date: Tue, 21 Feb 2023 08:52:57 +0100 Subject: [PATCH 061/178] Add from_utf8 --- examples/rpi-pico-w/src/main.rs | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/examples/rpi-pico-w/src/main.rs b/examples/rpi-pico-w/src/main.rs index e71c2234..c706e121 100644 --- a/examples/rpi-pico-w/src/main.rs +++ b/examples/rpi-pico-w/src/main.rs @@ -1,4 +1,4 @@ -#![no_std] +#![no_std] #![no_main] #![feature(type_alias_impl_trait)] #![feature(async_fn_in_trait)] @@ -18,8 +18,7 @@ use embedded_io::asynch::Write; use static_cell::StaticCell; use {defmt_rtt as _, panic_probe as _}; -use heapless::String; - +use core::str::from_utf8; macro_rules! singleton { ($val:expr) => {{ @@ -132,7 +131,7 @@ async fn main(spawner: Spawner) { } }; - info!("rxd {}", asciify(&buf[..n])); + info!("rxd {}", from_utf8(&buf[..n]).unwrap()); match socket.write_all(&buf[..n]).await { @@ -218,7 +217,3 @@ impl SpiBusWrite for MySpi { Ok(()) } } - -fn asciify(buf: &[u8],) -> String<4096> { - buf.into_iter().map(|c| *c as char).into_iter().collect() -} From d57fe0de867cfc6510f0192fab488355d9ae8586 Mon Sep 17 00:00:00 2001 From: kbleeke Date: Sun, 19 Feb 2023 16:31:33 +0100 Subject: [PATCH 062/178] Custom Bus Trait to support PIO --- examples/rpi-pico-w/build.rs | 34 +++++------ examples/rpi-pico-w/src/main.rs | 11 ++++ src/bus.rs | 100 +++++++++++++++++++++++--------- src/lib.rs | 8 ++- 4 files changed, 104 insertions(+), 49 deletions(-) diff --git a/examples/rpi-pico-w/build.rs b/examples/rpi-pico-w/build.rs index 3f915f93..d4c3ec89 100644 --- a/examples/rpi-pico-w/build.rs +++ b/examples/rpi-pico-w/build.rs @@ -14,23 +14,23 @@ use std::io::Write; use std::path::PathBuf; fn main() { - // Put `memory.x` in our output directory and ensure it's - // on the linker search path. - let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap()); - File::create(out.join("memory.x")) - .unwrap() - .write_all(include_bytes!("memory.x")) - .unwrap(); - println!("cargo:rustc-link-search={}", out.display()); + // // Put `memory.x` in our output directory and ensure it's + // // on the linker search path. + // let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap()); + // File::create(out.join("memory.x")) + // .unwrap() + // .write_all(include_bytes!("memory.x")) + // .unwrap(); + // println!("cargo:rustc-link-search={}", out.display()); - // By default, Cargo will re-run a build script whenever - // any file in the project changes. By specifying `memory.x` - // here, we ensure the build script is only re-run when - // `memory.x` is changed. - println!("cargo:rerun-if-changed=memory.x"); + // // By default, Cargo will re-run a build script whenever + // // any file in the project changes. By specifying `memory.x` + // // here, we ensure the build script is only re-run when + // // `memory.x` is changed. + // println!("cargo:rerun-if-changed=memory.x"); - println!("cargo:rustc-link-arg-bins=--nmagic"); - println!("cargo:rustc-link-arg-bins=-Tlink.x"); - println!("cargo:rustc-link-arg-bins=-Tlink-rp.x"); - println!("cargo:rustc-link-arg-bins=-Tdefmt.x"); + // println!("cargo:rustc-link-arg-bins=--nmagic"); + // println!("cargo:rustc-link-arg-bins=-Tlink.x"); + // println!("cargo:rustc-link-arg-bins=-Tlink-rp.x"); + // println!("cargo:rustc-link-arg-bins=-Tdefmt.x"); } diff --git a/examples/rpi-pico-w/src/main.rs b/examples/rpi-pico-w/src/main.rs index c706e121..f768af19 100644 --- a/examples/rpi-pico-w/src/main.rs +++ b/examples/rpi-pico-w/src/main.rs @@ -161,6 +161,17 @@ impl ErrorType for MySpi { type Error = Infallible; } +impl cyw43::SpiBusCyw43 for MySpi { + async fn cmd_write<'a>(&'a mut self, write: &'a [u32]) -> Result<(), Self::Error> { + self.write(write).await + } + + async fn cmd_read<'a>(&'a mut self, write: &'a [u32], read: &'a mut [u32]) -> Result<(), Self::Error> { + self.write(write).await?; + self.read(read).await + } +} + impl SpiBusFlush for MySpi { async fn flush(&mut self) -> Result<(), Self::Error> { Ok(()) diff --git a/src/bus.rs b/src/bus.rs index f64c0abb..1c8bb989 100644 --- a/src/bus.rs +++ b/src/bus.rs @@ -2,10 +2,23 @@ use core::slice; use embassy_time::{Duration, Timer}; use embedded_hal_1::digital::OutputPin; +use embedded_hal_1::spi::ErrorType; use embedded_hal_async::spi::{transaction, SpiBusRead, SpiBusWrite, SpiDevice}; use crate::consts::*; +/// Custom Spi Trait that _only_ supports the bus operation of the cyw43 +pub trait SpiBusCyw43: ErrorType { + /// Issues a write command on the bus + /// Frist 32 bits of `word` are expected to be a cmd word + async fn cmd_write<'a>(&'a mut self, write: &'a [Word]) -> Result<(), Self::Error>; + + /// Issues a read command on the bus + /// `write` is expected to be a 32 bit cmd word + /// `read` will contain the response of the device + async fn cmd_read<'a>(&'a mut self, write: &'a [Word], read: &'a mut [Word]) -> Result<(), Self::Error>; +} + pub(crate) struct Bus { backplane_window: u32, pwr: PWR, @@ -16,7 +29,7 @@ impl Bus where PWR: OutputPin, SPI: SpiDevice, - SPI::Bus: SpiBusRead + SpiBusWrite, + SPI::Bus: SpiBusCyw43, { pub(crate) fn new(pwr: PWR, spi: SPI) -> Self { Self { @@ -52,8 +65,9 @@ where let cmd = cmd_word(READ, INC_ADDR, FUNC_WLAN, 0, len_in_u8); let len_in_u32 = (len_in_u8 as usize + 3) / 4; transaction!(&mut self.spi, |bus| async { - bus.write(&[cmd]).await?; - bus.read(&mut buf[..len_in_u32]).await?; + // bus.write(&[cmd]).await?; + // bus.read(&mut buf[..len_in_u32]).await?; + bus.cmd_read(slice::from_ref(&cmd), &mut buf[..len_in_u32]).await?; Ok(()) }) .await @@ -62,9 +76,16 @@ where pub async fn wlan_write(&mut self, buf: &[u32]) { let cmd = cmd_word(WRITE, INC_ADDR, FUNC_WLAN, 0, buf.len() as u32 * 4); + //TODO try to remove copy? + let mut cmd_buf = [0_u32; 513]; + cmd_buf[0] = cmd; + cmd_buf[1..][..buf.len()].copy_from_slice(buf); + transaction!(&mut self.spi, |bus| async { - bus.write(&[cmd]).await?; - bus.write(buf).await?; + // bus.write(&[cmd]).await?; + // bus.write(buf).await?; + + bus.cmd_write(&cmd_buf).await?; Ok(()) }) .await @@ -79,7 +100,7 @@ where // To simplify, enforce 4-align for now. assert!(addr % 4 == 0); - let mut buf = [0u32; BACKPLANE_MAX_TRANSFER_SIZE / 4]; + let mut buf = [0u32; BACKPLANE_MAX_TRANSFER_SIZE / 4 + 1]; while !data.is_empty() { // Ensure transfer doesn't cross a window boundary. @@ -93,20 +114,23 @@ where let cmd = cmd_word(READ, INC_ADDR, FUNC_BACKPLANE, window_offs, len as u32); transaction!(&mut self.spi, |bus| async { - bus.write(&[cmd]).await?; + // bus.write(&[cmd]).await?; - // 4-byte response delay. - let mut junk = [0; 1]; - bus.read(&mut junk).await?; + // // 4-byte response delay. + // let mut junk = [0; 1]; + // bus.read(&mut junk).await?; - // Read data - bus.read(&mut buf[..(len + 3) / 4]).await?; + // // Read data + // bus.read(&mut buf[..(len + 3) / 4]).await?; + + bus.cmd_read(slice::from_ref(&cmd), &mut buf[..(len + 3) / 4 + 1]) + .await?; Ok(()) }) .await .unwrap(); - data[..len].copy_from_slice(&slice8_mut(&mut buf)[..len]); + data[..len].copy_from_slice(&slice8_mut(&mut buf[1..])[..len]); // Advance ptr. addr += len as u32; @@ -121,7 +145,7 @@ where // To simplify, enforce 4-align for now. assert!(addr % 4 == 0); - let mut buf = [0u32; BACKPLANE_MAX_TRANSFER_SIZE / 4]; + let mut buf = [0u32; BACKPLANE_MAX_TRANSFER_SIZE / 4 + 1]; while !data.is_empty() { // Ensure transfer doesn't cross a window boundary. @@ -129,15 +153,19 @@ where let window_remaining = BACKPLANE_WINDOW_SIZE - window_offs as usize; let len = data.len().min(BACKPLANE_MAX_TRANSFER_SIZE).min(window_remaining); - slice8_mut(&mut buf)[..len].copy_from_slice(&data[..len]); + slice8_mut(&mut buf[1..])[..len].copy_from_slice(&data[..len]); self.backplane_set_window(addr).await; let cmd = cmd_word(WRITE, INC_ADDR, FUNC_BACKPLANE, window_offs, len as u32); + buf[0] = cmd; transaction!(&mut self.spi, |bus| async { - bus.write(&[cmd]).await?; - bus.write(&buf[..(len + 3) / 4]).await?; + // bus.write(&[cmd]).await?; + // bus.write(&buf[..(len + 3) / 4]).await?; + + bus.cmd_write(&buf[..(len + 3) / 4 + 1]).await?; + Ok(()) }) .await @@ -253,28 +281,36 @@ where async fn readn(&mut self, func: u32, addr: u32, len: u32) -> u32 { let cmd = cmd_word(READ, INC_ADDR, func, addr, len); - let mut buf = [0; 1]; + let mut buf = [0; 2]; + let len = if func == FUNC_BACKPLANE { 2 } else { 1 }; transaction!(&mut self.spi, |bus| async { - bus.write(&[cmd]).await?; - if func == FUNC_BACKPLANE { - // 4-byte response delay. - bus.read(&mut buf).await?; - } - bus.read(&mut buf).await?; + // bus.write(&[cmd]).await?; + // if func == FUNC_BACKPLANE { + // // 4-byte response delay. + // bus.read(&mut buf).await?; + // } + // bus.read(&mut buf).await?; + + bus.cmd_read(slice::from_ref(&cmd), &mut buf[..len]).await?; Ok(()) }) .await .unwrap(); - buf[0] + if func == FUNC_BACKPLANE { + buf[1] + } else { + buf[0] + } } async fn writen(&mut self, func: u32, addr: u32, val: u32, len: u32) { let cmd = cmd_word(WRITE, INC_ADDR, func, addr, len); transaction!(&mut self.spi, |bus| async { - bus.write(&[cmd, val]).await?; + // bus.write(&[cmd, val]).await?; + bus.cmd_write(&[cmd, val]).await?; Ok(()) }) .await @@ -283,11 +319,14 @@ where async fn read32_swapped(&mut self, addr: u32) -> u32 { let cmd = cmd_word(READ, INC_ADDR, FUNC_BUS, addr, 4); + let cmd = swap16(cmd); let mut buf = [0; 1]; transaction!(&mut self.spi, |bus| async { - bus.write(&[swap16(cmd)]).await?; - bus.read(&mut buf).await?; + // bus.write(&[swap16(cmd)]).await?; + // bus.read(&mut buf).await?; + + bus.cmd_read(slice::from_ref(&cmd), &mut buf).await?; Ok(()) }) .await @@ -298,9 +337,12 @@ where async fn write32_swapped(&mut self, addr: u32, val: u32) { let cmd = cmd_word(WRITE, INC_ADDR, FUNC_BUS, addr, 4); + let buf = [swap16(cmd), swap16(val)]; transaction!(&mut self.spi, |bus| async { - bus.write(&[swap16(cmd), swap16(val)]).await?; + // bus.write(&[swap16(cmd), swap16(val)]).await?; + + bus.cmd_write(&buf).await?; Ok(()) }) .await diff --git a/src/lib.rs b/src/lib.rs index 5733506a..7bf3992c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,7 @@ #![no_std] #![no_main] -#![feature(type_alias_impl_trait, concat_bytes)] +#![allow(incomplete_features)] +#![feature(async_fn_in_trait, type_alias_impl_trait, concat_bytes)] #![deny(unused_must_use)] // This mod MUST go first, so that the others see its macros. @@ -24,6 +25,7 @@ use embedded_hal_1::digital::OutputPin; use embedded_hal_async::spi::{SpiBusRead, SpiBusWrite, SpiDevice}; use crate::bus::Bus; +pub use crate::bus::SpiBusCyw43; use crate::consts::*; use crate::events::Event; use crate::structs::*; @@ -512,7 +514,7 @@ pub async fn new<'a, PWR, SPI>( where PWR: OutputPin, SPI: SpiDevice, - SPI::Bus: SpiBusRead + SpiBusWrite, + SPI::Bus: SpiBusCyw43, { let (ch_runner, device) = ch::new(&mut state.ch, [0; 6]); let state_ch = ch_runner.state_runner(); @@ -551,7 +553,7 @@ impl<'a, PWR, SPI> Runner<'a, PWR, SPI> where PWR: OutputPin, SPI: SpiDevice, - SPI::Bus: SpiBusRead + SpiBusWrite, + SPI::Bus: SpiBusCyw43, { async fn init(&mut self, firmware: &[u8]) { self.bus.init().await; From 0ff606dfc151b1b3812087b7508fdf4bee3b240b Mon Sep 17 00:00:00 2001 From: kbleeke Date: Sun, 19 Feb 2023 16:31:35 +0100 Subject: [PATCH 063/178] Add pio transport to pico w example --- examples/rpi-pico-w/Cargo.toml | 40 +++++-- examples/rpi-pico-w/build.rs | 34 +++--- examples/rpi-pico-w/src/main.rs | 29 +++-- examples/rpi-pico-w/src/pio.rs | 190 ++++++++++++++++++++++++++++++++ src/bus.rs | 2 +- src/lib.rs | 2 +- 6 files changed, 263 insertions(+), 34 deletions(-) create mode 100644 examples/rpi-pico-w/src/pio.rs diff --git a/examples/rpi-pico-w/Cargo.toml b/examples/rpi-pico-w/Cargo.toml index 99b82ca3..0d789a93 100644 --- a/examples/rpi-pico-w/Cargo.toml +++ b/examples/rpi-pico-w/Cargo.toml @@ -5,11 +5,31 @@ edition = "2021" [dependencies] -cyw43 = { path = "../../", features = ["defmt", "firmware-logs"]} -embassy-executor = { version = "0.1.0", features = ["defmt", "integrated-timers"] } -embassy-time = { version = "0.1.0", features = ["defmt", "defmt-timestamp-uptime"] } -embassy-rp = { version = "0.1.0", features = ["defmt", "unstable-traits", "nightly", "unstable-pac", "time-driver"] } -embassy-net = { version = "0.1.0", features = ["defmt", "tcp", "dhcpv4", "medium-ethernet", "unstable-traits", "nightly"] } +cyw43 = { path = "../../", features = ["defmt", "firmware-logs"] } +embassy-executor = { version = "0.1.0", features = [ + "defmt", + "integrated-timers", +] } +embassy-time = { version = "0.1.0", features = [ + "defmt", + "defmt-timestamp-uptime", +] } +embassy-rp = { version = "0.1.0", features = [ + "defmt", + "unstable-traits", + "nightly", + "unstable-pac", + "pio", + "time-driver", +] } +embassy-net = { version = "0.1.0", features = [ + "defmt", + "tcp", + "dhcpv4", + "medium-ethernet", + "unstable-traits", + "nightly", +] } atomic-polyfill = "0.1.5" static_cell = "1.0" @@ -17,9 +37,15 @@ defmt = "0.3" defmt-rtt = "0.3" panic-probe = { version = "0.3", features = ["print-defmt"] } -cortex-m = { version = "0.7.6", features = ["critical-section-single-core"]} +cortex-m = { version = "0.7.6", features = ["critical-section-single-core"] } cortex-m-rt = "0.7.0" -futures = { version = "0.3.17", default-features = false, features = ["async-await", "cfg-target-has-atomic", "unstable"] } +futures = { version = "0.3.17", default-features = false, features = [ + "async-await", + "cfg-target-has-atomic", + "unstable", +] } +pio-proc = "0.2" +pio = "0.2.1" embedded-hal-1 = { package = "embedded-hal", version = "1.0.0-alpha.9" } embedded-hal-async = { version = "0.2.0-alpha.0" } diff --git a/examples/rpi-pico-w/build.rs b/examples/rpi-pico-w/build.rs index d4c3ec89..3f915f93 100644 --- a/examples/rpi-pico-w/build.rs +++ b/examples/rpi-pico-w/build.rs @@ -14,23 +14,23 @@ use std::io::Write; use std::path::PathBuf; fn main() { - // // Put `memory.x` in our output directory and ensure it's - // // on the linker search path. - // let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap()); - // File::create(out.join("memory.x")) - // .unwrap() - // .write_all(include_bytes!("memory.x")) - // .unwrap(); - // println!("cargo:rustc-link-search={}", out.display()); + // Put `memory.x` in our output directory and ensure it's + // on the linker search path. + let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap()); + File::create(out.join("memory.x")) + .unwrap() + .write_all(include_bytes!("memory.x")) + .unwrap(); + println!("cargo:rustc-link-search={}", out.display()); - // // By default, Cargo will re-run a build script whenever - // // any file in the project changes. By specifying `memory.x` - // // here, we ensure the build script is only re-run when - // // `memory.x` is changed. - // println!("cargo:rerun-if-changed=memory.x"); + // By default, Cargo will re-run a build script whenever + // any file in the project changes. By specifying `memory.x` + // here, we ensure the build script is only re-run when + // `memory.x` is changed. + println!("cargo:rerun-if-changed=memory.x"); - // println!("cargo:rustc-link-arg-bins=--nmagic"); - // println!("cargo:rustc-link-arg-bins=-Tlink.x"); - // println!("cargo:rustc-link-arg-bins=-Tlink-rp.x"); - // println!("cargo:rustc-link-arg-bins=-Tdefmt.x"); + println!("cargo:rustc-link-arg-bins=--nmagic"); + println!("cargo:rustc-link-arg-bins=-Tlink.x"); + println!("cargo:rustc-link-arg-bins=-Tlink-rp.x"); + println!("cargo:rustc-link-arg-bins=-Tdefmt.x"); } diff --git a/examples/rpi-pico-w/src/main.rs b/examples/rpi-pico-w/src/main.rs index f768af19..3563d165 100644 --- a/examples/rpi-pico-w/src/main.rs +++ b/examples/rpi-pico-w/src/main.rs @@ -4,21 +4,25 @@ #![feature(async_fn_in_trait)] #![allow(incomplete_features)] +mod pio; + use core::convert::Infallible; +use core::str::from_utf8; use defmt::*; use embassy_executor::Spawner; use embassy_net::tcp::TcpSocket; use embassy_net::{Config, Stack, StackResources}; use embassy_rp::gpio::{Flex, Level, Output}; -use embassy_rp::peripherals::{PIN_23, PIN_24, PIN_25, PIN_29}; +use embassy_rp::peripherals::{DMA_CH0, PIN_23, PIN_24, PIN_25, PIN_29}; +use embassy_rp::pio::{Pio0, PioPeripherial, PioStateMachineInstance, Sm0}; use embedded_hal_1::spi::ErrorType; use embedded_hal_async::spi::{ExclusiveDevice, SpiBusFlush, SpiBusRead, SpiBusWrite}; use embedded_io::asynch::Write; use static_cell::StaticCell; use {defmt_rtt as _, panic_probe as _}; -use core::str::from_utf8; +use crate::pio::PioSpi; macro_rules! singleton { ($val:expr) => {{ @@ -30,7 +34,11 @@ macro_rules! singleton { #[embassy_executor::task] async fn wifi_task( - runner: cyw43::Runner<'static, Output<'static, PIN_23>, ExclusiveDevice>>, + runner: cyw43::Runner< + 'static, + Output<'static, PIN_23>, + ExclusiveDevice, DMA_CH0>, Output<'static, PIN_25>>, + >, ) -> ! { runner.run().await } @@ -59,12 +67,15 @@ async fn main(spawner: Spawner) { let pwr = Output::new(p.PIN_23, Level::Low); let cs = Output::new(p.PIN_25, Level::High); - let clk = Output::new(p.PIN_29, Level::Low); - let mut dio = Flex::new(p.PIN_24); - dio.set_low(); - dio.set_as_output(); + // let clk = Output::new(p.PIN_29, Level::Low); + // let mut dio = Flex::new(p.PIN_24); + // dio.set_low(); + // dio.set_as_output(); + // // let bus = MySpi { clk, dio }; - let bus = MySpi { clk, dio }; + let (_, sm, _, _, _) = p.PIO0.split(); + let dma = p.DMA_CH0; + let bus = PioSpi::new(sm, p.PIN_24, p.PIN_29, dma); let spi = ExclusiveDevice::new(bus, cs); let state = singleton!(cyw43::State::new()); @@ -110,6 +121,7 @@ async fn main(spawner: Spawner) { let mut socket = TcpSocket::new(stack, &mut rx_buffer, &mut tx_buffer); socket.set_timeout(Some(embassy_net::SmolDuration::from_secs(10))); + control.gpio_set(0, false).await; info!("Listening on TCP:1234..."); if let Err(e) = socket.accept(1234).await { warn!("accept error: {:?}", e); @@ -117,6 +129,7 @@ async fn main(spawner: Spawner) { } info!("Received connection from {:?}", socket.remote_endpoint()); + control.gpio_set(0, true).await; loop { let n = match socket.read(&mut buf).await { diff --git a/examples/rpi-pico-w/src/pio.rs b/examples/rpi-pico-w/src/pio.rs new file mode 100644 index 00000000..abb71b5d --- /dev/null +++ b/examples/rpi-pico-w/src/pio.rs @@ -0,0 +1,190 @@ +use core::slice; + +use cyw43::SpiBusCyw43; +use embassy_rp::dma::Channel; +use embassy_rp::gpio::{Pin, Pull}; +use embassy_rp::pio::{PioStateMachine, ShiftDirection}; +use embassy_rp::relocate::RelocatedProgram; +use embassy_rp::{pio_instr_util, Peripheral}; +use embedded_hal_1::spi::ErrorType; +use embedded_hal_async::spi::SpiBusFlush; +use pio::Wrap; +use pio_proc::pio_asm; + +pub struct PioSpi { + // cs: Output<'static, AnyPin>, + sm: SM, + dma: DMA, + wrap_target: u8, +} + +impl PioSpi +where + SM: PioStateMachine, + DMA: Channel, +{ + pub fn new( + mut sm: SM, + // cs: AnyPin, + dio: DIO, + clk: CLK, + dma: DMA, + ) -> Self + where + DIO: Pin, + CLK: Pin, + { + let program = pio_asm!( + ".side_set 1" + // "set pindirs, 1 side 0" + // "set pins, 0 side 0" + ".wrap_target" + "lp:", + "out pins, 1 side 0" + "jmp x-- lp side 1" + "set pindirs, 0 side 0" + // "nop side 1" + "lp2:" + "in pins, 1 side 0" + "jmp y-- lp2 side 1" + ".wrap" + ); + + let relocated = RelocatedProgram::new(&program.program); + + let mut pin_io = sm.make_pio_pin(dio); + pin_io.set_pull(Pull::Down); + pin_io.set_schmitt(true); + let pin_clk = sm.make_pio_pin(clk); + + sm.write_instr(relocated.origin() as usize, relocated.code()); + + // 16 Mhz + sm.set_clkdiv(0x07d0); + + // 8Mhz + sm.set_clkdiv(0x0a_00); + + // 1Mhz + // sm.set_clkdiv(0x7d_00); + + // slowest possible + // sm.set_clkdiv(0xffff_00); + + sm.set_autopull(true); + // sm.set_pull_threshold(32); + sm.set_autopush(true); + // sm.set_push_threshold(32); + + sm.set_out_pins(&[&pin_io]); + sm.set_in_base_pin(&pin_io); + + sm.set_set_pins(&[&pin_clk]); + pio_instr_util::set_pindir(&mut sm, 0b1); + sm.set_set_pins(&[&pin_io]); + pio_instr_util::set_pindir(&mut sm, 0b1); + + sm.set_sideset_base_pin(&pin_clk); + sm.set_sideset_count(1); + + sm.set_out_shift_dir(ShiftDirection::Left); + sm.set_in_shift_dir(ShiftDirection::Left); + + let Wrap { source, target } = relocated.wrap(); + sm.set_wrap(source, target); + + // pull low for startup + pio_instr_util::set_pin(&mut sm, 0); + + Self { + // cs: Output::new(cs, Level::High), + sm, + dma, + wrap_target: target, + } + } + + pub async fn write(&mut self, write: &[u32]) { + let write_bits = write.len() * 32 - 1; + let read_bits = 31; + + defmt::trace!("write={} read={}", write_bits, read_bits); + + let mut dma = Peripheral::into_ref(&mut self.dma); + pio_instr_util::set_x(&mut self.sm, write_bits as u32); + pio_instr_util::set_y(&mut self.sm, read_bits as u32); + pio_instr_util::set_pindir(&mut self.sm, 0b1); + pio_instr_util::exec_jmp(&mut self.sm, self.wrap_target); + + self.sm.set_enable(true); + + self.sm.dma_push(dma.reborrow(), write).await; + + let mut status = 0; + self.sm.dma_pull(dma, slice::from_mut(&mut status)).await; + defmt::trace!("{:#08x}", status); + + self.sm.set_enable(false); + } + + pub async fn cmd_read(&mut self, cmd: u32, read: &mut [u32]) { + let write_bits = 31; + let read_bits = read.len() * 32 - 1; + + defmt::trace!("write={} read={}", write_bits, read_bits); + + let mut dma = Peripheral::into_ref(&mut self.dma); + pio_instr_util::set_y(&mut self.sm, read_bits as u32); + pio_instr_util::set_x(&mut self.sm, write_bits as u32); + pio_instr_util::set_pindir(&mut self.sm, 0b1); + pio_instr_util::exec_jmp(&mut self.sm, self.wrap_target); + // self.cs.set_low(); + self.sm.set_enable(true); + + self.sm.dma_push(dma.reborrow(), slice::from_ref(&cmd)).await; + self.sm.dma_pull(dma, read).await; + + self.sm.set_enable(false); + } +} + +#[derive(Debug)] +pub enum PioError {} + +impl embedded_hal_async::spi::Error for PioError { + fn kind(&self) -> embedded_hal_1::spi::ErrorKind { + embedded_hal_1::spi::ErrorKind::Other + } +} + +impl ErrorType for PioSpi +where + SM: PioStateMachine, +{ + type Error = PioError; +} + +impl SpiBusFlush for PioSpi +where + SM: PioStateMachine, +{ + async fn flush(&mut self) -> Result<(), Self::Error> { + Ok(()) + } +} + +impl SpiBusCyw43 for PioSpi +where + SM: PioStateMachine, + DMA: Channel, +{ + async fn cmd_write<'a>(&'a mut self, write: &'a [u32]) -> Result<(), Self::Error> { + self.write(write).await; + Ok(()) + } + + async fn cmd_read<'a>(&'a mut self, write: &'a [u32], read: &'a mut [u32]) -> Result<(), Self::Error> { + self.cmd_read(write[0], read).await; + Ok(()) + } +} diff --git a/src/bus.rs b/src/bus.rs index 1c8bb989..aaa79b19 100644 --- a/src/bus.rs +++ b/src/bus.rs @@ -3,7 +3,7 @@ use core::slice; use embassy_time::{Duration, Timer}; use embedded_hal_1::digital::OutputPin; use embedded_hal_1::spi::ErrorType; -use embedded_hal_async::spi::{transaction, SpiBusRead, SpiBusWrite, SpiDevice}; +use embedded_hal_async::spi::{transaction, SpiDevice}; use crate::consts::*; diff --git a/src/lib.rs b/src/lib.rs index 7bf3992c..bcc3c59b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -22,7 +22,7 @@ use embassy_futures::yield_now; use embassy_net_driver_channel as ch; use embassy_time::{block_for, Duration, Timer}; use embedded_hal_1::digital::OutputPin; -use embedded_hal_async::spi::{SpiBusRead, SpiBusWrite, SpiDevice}; +use embedded_hal_async::spi::SpiDevice; use crate::bus::Bus; pub use crate::bus::SpiBusCyw43; From a6a2a035d57ced9a7a9bb2ef325885063ea83295 Mon Sep 17 00:00:00 2001 From: kbleeke Date: Sun, 19 Mar 2023 16:43:46 +0100 Subject: [PATCH 064/178] even faster pio speed are possible --- examples/rpi-pico-w/src/pio.rs | 22 +++++++++++++++------- rust-toolchain.toml | 2 +- src/bus.rs | 31 +++++++++++++++++++++++++++---- 3 files changed, 43 insertions(+), 12 deletions(-) diff --git a/examples/rpi-pico-w/src/pio.rs b/examples/rpi-pico-w/src/pio.rs index abb71b5d..1bf304d5 100644 --- a/examples/rpi-pico-w/src/pio.rs +++ b/examples/rpi-pico-w/src/pio.rs @@ -2,7 +2,7 @@ use core::slice; use cyw43::SpiBusCyw43; use embassy_rp::dma::Channel; -use embassy_rp::gpio::{Pin, Pull}; +use embassy_rp::gpio::{Drive, Pin, Pull, SlewRate}; use embassy_rp::pio::{PioStateMachine, ShiftDirection}; use embassy_rp::relocate::RelocatedProgram; use embassy_rp::{pio_instr_util, Peripheral}; @@ -43,10 +43,11 @@ where "out pins, 1 side 0" "jmp x-- lp side 1" "set pindirs, 0 side 0" - // "nop side 1" + "nop side 1" "lp2:" - "in pins, 1 side 0" - "jmp y-- lp2 side 1" + "in pins, 1 side 1" + "jmp y-- lp2 side 0" + ".wrap" ); @@ -55,15 +56,22 @@ where let mut pin_io = sm.make_pio_pin(dio); pin_io.set_pull(Pull::Down); pin_io.set_schmitt(true); - let pin_clk = sm.make_pio_pin(clk); + pin_io.set_input_sync_bypass(true); + + let mut pin_clk = sm.make_pio_pin(clk); + pin_clk.set_drive_strength(Drive::_12mA); + pin_clk.set_slew_rate(SlewRate::Fast); sm.write_instr(relocated.origin() as usize, relocated.code()); + // 32 Mhz + sm.set_clkdiv(0x03E8); + // 16 Mhz - sm.set_clkdiv(0x07d0); + // sm.set_clkdiv(0x07d0); // 8Mhz - sm.set_clkdiv(0x0a_00); + // sm.set_clkdiv(0x0a_00); // 1Mhz // sm.set_clkdiv(0x7d_00); diff --git a/rust-toolchain.toml b/rust-toolchain.toml index ffbcbd6f..20c10c3f 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,7 +1,7 @@ # Before upgrading check that everything is available on all tier1 targets here: # https://rust-lang.github.io/rustup-components-history [toolchain] -channel = "nightly-2022-11-22" +channel = "nightly-2023-03-19" components = [ "rust-src", "rustfmt" ] targets = [ "thumbv6m-none-eabi", diff --git a/src/bus.rs b/src/bus.rs index aaa79b19..e4f9a69b 100644 --- a/src/bus.rs +++ b/src/bus.rs @@ -4,6 +4,7 @@ use embassy_time::{Duration, Timer}; use embedded_hal_1::digital::OutputPin; use embedded_hal_1::spi::ErrorType; use embedded_hal_async::spi::{transaction, SpiDevice}; +use futures::FutureExt; use crate::consts::*; @@ -46,18 +47,40 @@ where self.pwr.set_high().unwrap(); Timer::after(Duration::from_millis(250)).await; - while self.read32_swapped(REG_BUS_TEST_RO).await != FEEDBEAD {} + while self + .read32_swapped(REG_BUS_TEST_RO) + .inspect(|v| defmt::trace!("{:#x}", v)) + .await + != FEEDBEAD + {} self.write32_swapped(REG_BUS_TEST_RW, TEST_PATTERN).await; - let val = self.read32_swapped(REG_BUS_TEST_RW).await; + let val = self + .read32_swapped(REG_BUS_TEST_RW) + .inspect(|v| defmt::trace!("{:#x}", v)) + .await; assert_eq!(val, TEST_PATTERN); + self.read32_swapped(REG_BUS_CTRL) + .inspect(|v| defmt::trace!("{:#010b}", (v & 0xff))) + .await; + // 32-bit word length, little endian (which is the default endianess). self.write32_swapped(REG_BUS_CTRL, WORD_LENGTH_32 | HIGH_SPEED).await; - let val = self.read32(FUNC_BUS, REG_BUS_TEST_RO).await; + self.read8(FUNC_BUS, REG_BUS_CTRL) + .inspect(|v| defmt::trace!("{:#b}", v)) + .await; + + let val = self + .read32(FUNC_BUS, REG_BUS_TEST_RO) + .inspect(|v| defmt::trace!("{:#x}", v)) + .await; assert_eq!(val, FEEDBEAD); - let val = self.read32(FUNC_BUS, REG_BUS_TEST_RW).await; + let val = self + .read32(FUNC_BUS, REG_BUS_TEST_RW) + .inspect(|v| defmt::trace!("{:#x}", v)) + .await; assert_eq!(val, TEST_PATTERN); } From 1b410d6f3f08f12f2bd250a8b76f217291f4df26 Mon Sep 17 00:00:00 2001 From: kbleeke Date: Wed, 1 Mar 2023 19:03:46 +0100 Subject: [PATCH 065/178] add event handling to join --- examples/rpi-pico-w/src/main.rs | 9 ++++--- src/events.rs | 14 +++++++++++ src/lib.rs | 42 +++++++++++++++++++++++++++------ 3 files changed, 53 insertions(+), 12 deletions(-) diff --git a/examples/rpi-pico-w/src/main.rs b/examples/rpi-pico-w/src/main.rs index c706e121..91caa5e3 100644 --- a/examples/rpi-pico-w/src/main.rs +++ b/examples/rpi-pico-w/src/main.rs @@ -70,16 +70,11 @@ async fn main(spawner: Spawner) { let state = singleton!(cyw43::State::new()); let (net_device, mut control, runner) = cyw43::new(state, pwr, spi, fw).await; - spawner.spawn(wifi_task(runner)).unwrap(); - control.init(clm).await; control .set_power_management(cyw43::PowerManagementMode::PowerSave) .await; - //control.join_open(env!("WIFI_NETWORK")).await; - control.join_wpa2(env!("WIFI_NETWORK"), env!("WIFI_PASSWORD")).await; - let config = Config::Dhcp(Default::default()); //let config = embassy_net::Config::Static(embassy_net::Config { // address: Ipv4Cidr::new(Ipv4Address::new(192, 168, 69, 2), 24), @@ -98,8 +93,12 @@ async fn main(spawner: Spawner) { seed )); + unwrap!(spawner.spawn(wifi_task(runner))); unwrap!(spawner.spawn(net_task(stack))); + //control.join_open(env!("WIFI_NETWORK")).await; + control.join_wpa2(env!("WIFI_NETWORK"), env!("WIFI_PASSWORD")).await; + // And now we can use it! let mut rx_buffer = [0; 4096]; diff --git a/src/events.rs b/src/events.rs index a828eec9..9e6bb962 100644 --- a/src/events.rs +++ b/src/events.rs @@ -3,6 +3,9 @@ use core::num; +use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; +use embassy_sync::pubsub::{PubSubChannel, Publisher, Subscriber}; + #[derive(Clone, Copy, PartialEq, Eq, num_enum::FromPrimitive)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] #[repr(u8)] @@ -280,3 +283,14 @@ pub enum Event { /// highest val + 1 for range checking LAST = 190, } + +pub type EventQueue = PubSubChannel; +pub type EventPublisher<'a> = Publisher<'a, CriticalSectionRawMutex, EventStatus, 2, 1, 1>; +pub type EventSubscriber<'a> = Subscriber<'a, CriticalSectionRawMutex, EventStatus, 2, 1, 1>; + +#[derive(Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct EventStatus { + pub event_type: Event, + pub status: u32, +} diff --git a/src/lib.rs b/src/lib.rs index 5733506a..c58ac8e7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -19,13 +19,15 @@ use core::slice; use ch::driver::LinkState; use embassy_futures::yield_now; use embassy_net_driver_channel as ch; +use embassy_sync::pubsub::PubSubBehavior; use embassy_time::{block_for, Duration, Timer}; use embedded_hal_1::digital::OutputPin; use embedded_hal_async::spi::{SpiBusRead, SpiBusWrite, SpiDevice}; +use events::EventQueue; use crate::bus::Bus; use crate::consts::*; -use crate::events::Event; +use crate::events::{Event, EventStatus}; use crate::structs::*; const MTU: usize = 1514; @@ -127,6 +129,7 @@ enum IoctlState { pub struct State { ioctl_state: Cell, ch: ch::State, + events: EventQueue, } impl State { @@ -134,12 +137,14 @@ impl State { Self { ioctl_state: Cell::new(IoctlState::Idle), ch: ch::State::new(), + events: EventQueue::new(), } } } pub struct Control<'a> { state_ch: ch::StateRunner<'a>, + event_sub: &'a EventQueue, ioctl_state: &'a Cell, } @@ -313,6 +318,7 @@ impl<'a> Control<'a> { evts.unset(Event::PROBREQ_MSG_RX); evts.unset(Event::PROBRESP_MSG); evts.unset(Event::PROBRESP_MSG); + evts.unset(Event::ROAM); self.set_iovar("bsscfg:event_msgs", &evts.to_bytes()).await; @@ -393,8 +399,22 @@ impl<'a> Control<'a> { ssid: [0; 32], }; i.ssid[..ssid.len()].copy_from_slice(ssid.as_bytes()); + + let mut subscriber = self.event_sub.subscriber().unwrap(); self.ioctl(IoctlType::Set, 26, 0, &mut i.to_bytes()).await; // set_ssid + loop { + let msg = subscriber.next_message_pure().await; + if msg.event_type == Event::AUTH && msg.status != 0 { + // retry + defmt::warn!("JOIN failed with status={}", msg.status); + self.ioctl(IoctlType::Set, 26, 0, &mut i.to_bytes()).await; + } else if msg.event_type == Event::JOIN && msg.status == 0 { + // successful join + break; + } + } + info!("JOINED"); } @@ -489,6 +509,8 @@ pub struct Runner<'a, PWR, SPI> { sdpcm_seq: u8, sdpcm_seq_max: u8, + events: &'a EventQueue, + #[cfg(feature = "firmware-logs")] log: LogState, } @@ -526,6 +548,8 @@ where sdpcm_seq: 0, sdpcm_seq_max: 1, + events: &state.events, + #[cfg(feature = "firmware-logs")] log: LogState { addr: 0, @@ -541,6 +565,7 @@ where device, Control { state_ch, + event_sub: &&state.events, ioctl_state: &state.ioctl_state, }, runner, @@ -883,13 +908,16 @@ where return; } + let evt_type = events::Event::from(event_packet.msg.event_type as u8); let evt_data = &bcd_packet[EventMessage::SIZE..][..event_packet.msg.datalen as usize]; - debug!( - "=== EVENT {}: {} {:02x}", - events::Event::from(event_packet.msg.event_type as u8), - event_packet.msg, - evt_data - ); + debug!("=== EVENT {}: {} {:02x}", evt_type, event_packet.msg, evt_data); + + if evt_type == events::Event::AUTH || evt_type == events::Event::JOIN { + self.events.publish_immediate(EventStatus { + status: event_packet.msg.status, + event_type: evt_type, + }); + } } CHANNEL_TYPE_DATA => { let bcd_header = BcdHeader::from_bytes(&payload[..BcdHeader::SIZE].try_into().unwrap()); From 67743bb1221fefc677bd2f207d0c382a85a46b7f Mon Sep 17 00:00:00 2001 From: Jacob Davis-Hansson Date: Sun, 19 Mar 2023 19:16:26 +0100 Subject: [PATCH 066/178] Update pre-flashed command to match file name Super minor, just to help the next person avoid the little stumble. --- examples/rpi-pico-w/src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/rpi-pico-w/src/main.rs b/examples/rpi-pico-w/src/main.rs index c706e121..ad4e9895 100644 --- a/examples/rpi-pico-w/src/main.rs +++ b/examples/rpi-pico-w/src/main.rs @@ -53,7 +53,7 @@ async fn main(spawner: Spawner) { // To make flashing faster for development, you may want to flash the firmwares independently // at hardcoded addresses, instead of baking them into the program with `include_bytes!`: // probe-rs-cli download 43439A0.bin --format bin --chip RP2040 --base-address 0x10100000 - // probe-rs-cli download 43439A0.clm_blob --format bin --chip RP2040 --base-address 0x10140000 + // probe-rs-cli download 43439A0_clm.bin --format bin --chip RP2040 --base-address 0x10140000 //let fw = unsafe { core::slice::from_raw_parts(0x10100000 as *const u8, 224190) }; //let clm = unsafe { core::slice::from_raw_parts(0x10140000 as *const u8, 4752) }; From b411b7ce637e3e561f43b0c4f020f02b5607467b Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Sun, 19 Mar 2023 22:36:18 +0100 Subject: [PATCH 067/178] vscode: recommend extensions, disable toml formatting, update. --- .vscode/extensions.json | 11 +++++++++++ .vscode/settings.json | 14 ++++++-------- 2 files changed, 17 insertions(+), 8 deletions(-) create mode 100644 .vscode/extensions.json diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 00000000..a8bb78ad --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,11 @@ +{ + // See https://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations. + // Extension identifier format: ${publisher}.${name}. Example: vscode.csharp + // List of extensions which should be recommended for users of this workspace. + "recommendations": [ + "rust-lang.rust-analyzer", + "tamasfe.even-better-toml", + ], + // List of extensions recommended by VS Code that should not be recommended for users of this workspace. + "unwantedRecommendations": [] +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index 082b286d..dd479929 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,14 +1,12 @@ { "editor.formatOnSave": true, - "rust-analyzer.cargo.buildScripts.enable": true, - "rust-analyzer.cargo.noDefaultFeatures": true, + "[toml]": { + "editor.formatOnSave": false + }, "rust-analyzer.cargo.target": "thumbv6m-none-eabi", - "rust-analyzer.checkOnSave.allTargets": false, - "rust-analyzer.checkOnSave.noDefaultFeatures": true, - "rust-analyzer.imports.granularity.enforce": true, - "rust-analyzer.imports.granularity.group": "module", - "rust-analyzer.procMacro.attributes.enable": false, - "rust-analyzer.procMacro.enable": false, + "rust-analyzer.cargo.noDefaultFeatures": true, + "rust-analyzer.check.allTargets": false, + "rust-analyzer.check.noDefaultFeatures": true, "rust-analyzer.linkedProjects": [ "examples/rpi-pico-w/Cargo.toml", ], From b4b8d829801e149c90f9f0fc85736be3549dff87 Mon Sep 17 00:00:00 2001 From: kbleeke Date: Tue, 21 Mar 2023 19:15:54 +0100 Subject: [PATCH 068/178] remove use of embedded-hal SPI traits. Instead just call our bus trait directly and push responsibility for implementing CS on the trait implementor --- Cargo.toml | 1 - examples/rpi-pico-w/Cargo.toml | 2 - examples/rpi-pico-w/src/main.rs | 62 ++++++++----------- examples/rpi-pico-w/src/pio.rs | 61 +++++-------------- src/bus.rs | 104 +++++--------------------------- src/lib.rs | 7 +-- 6 files changed, 59 insertions(+), 178 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 6e323744..3bdeb0cf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,5 +25,4 @@ cortex-m-rt = "0.7.0" futures = { version = "0.3.17", default-features = false, features = ["async-await", "cfg-target-has-atomic", "unstable"] } embedded-hal-1 = { package = "embedded-hal", version = "1.0.0-alpha.9" } -embedded-hal-async = { version = "0.2.0-alpha.0" } num_enum = { version = "0.5.7", default-features = false } diff --git a/examples/rpi-pico-w/Cargo.toml b/examples/rpi-pico-w/Cargo.toml index 0d789a93..17b4214d 100644 --- a/examples/rpi-pico-w/Cargo.toml +++ b/examples/rpi-pico-w/Cargo.toml @@ -47,8 +47,6 @@ futures = { version = "0.3.17", default-features = false, features = [ pio-proc = "0.2" pio = "0.2.1" -embedded-hal-1 = { package = "embedded-hal", version = "1.0.0-alpha.9" } -embedded-hal-async = { version = "0.2.0-alpha.0" } embedded-io = { version = "0.4.0", features = ["async", "defmt"] } heapless = "0.7.15" diff --git a/examples/rpi-pico-w/src/main.rs b/examples/rpi-pico-w/src/main.rs index 3563d165..f30a20ba 100644 --- a/examples/rpi-pico-w/src/main.rs +++ b/examples/rpi-pico-w/src/main.rs @@ -1,4 +1,4 @@ -#![no_std] +#![no_std] #![no_main] #![feature(type_alias_impl_trait)] #![feature(async_fn_in_trait)] @@ -6,7 +6,7 @@ mod pio; -use core::convert::Infallible; +use core::slice; use core::str::from_utf8; use defmt::*; @@ -16,8 +16,6 @@ use embassy_net::{Config, Stack, StackResources}; use embassy_rp::gpio::{Flex, Level, Output}; use embassy_rp::peripherals::{DMA_CH0, PIN_23, PIN_24, PIN_25, PIN_29}; use embassy_rp::pio::{Pio0, PioPeripherial, PioStateMachineInstance, Sm0}; -use embedded_hal_1::spi::ErrorType; -use embedded_hal_async::spi::{ExclusiveDevice, SpiBusFlush, SpiBusRead, SpiBusWrite}; use embedded_io::asynch::Write; use static_cell::StaticCell; use {defmt_rtt as _, panic_probe as _}; @@ -37,7 +35,7 @@ async fn wifi_task( runner: cyw43::Runner< 'static, Output<'static, PIN_23>, - ExclusiveDevice, DMA_CH0>, Output<'static, PIN_25>>, + PioSpi, DMA_CH0>, >, ) -> ! { runner.run().await @@ -75,8 +73,7 @@ async fn main(spawner: Spawner) { let (_, sm, _, _, _) = p.PIO0.split(); let dma = p.DMA_CH0; - let bus = PioSpi::new(sm, p.PIN_24, p.PIN_29, dma); - let spi = ExclusiveDevice::new(bus, cs); + let spi = PioSpi::new(sm, cs, p.PIN_24, p.PIN_29, dma); let state = singleton!(cyw43::State::new()); let (net_device, mut control, runner) = cyw43::new(state, pwr, spi, fw).await; @@ -146,7 +143,6 @@ async fn main(spawner: Spawner) { info!("rxd {}", from_utf8(&buf[..n]).unwrap()); - match socket.write_all(&buf[..n]).await { Ok(()) => {} Err(e) => { @@ -168,31 +164,13 @@ struct MySpi { /// - IRQ /// - strap to set to gSPI mode on boot. dio: Flex<'static, PIN_24>, + + /// Chip select + cs: Output<'static, PIN_25>, } -impl ErrorType for MySpi { - type Error = Infallible; -} - -impl cyw43::SpiBusCyw43 for MySpi { - async fn cmd_write<'a>(&'a mut self, write: &'a [u32]) -> Result<(), Self::Error> { - self.write(write).await - } - - async fn cmd_read<'a>(&'a mut self, write: &'a [u32], read: &'a mut [u32]) -> Result<(), Self::Error> { - self.write(write).await?; - self.read(read).await - } -} - -impl SpiBusFlush for MySpi { - async fn flush(&mut self) -> Result<(), Self::Error> { - Ok(()) - } -} - -impl SpiBusRead for MySpi { - async fn read(&mut self, words: &mut [u32]) -> Result<(), Self::Error> { +impl MySpi { + async fn read(&mut self, words: &mut [u32]) { self.dio.set_as_input(); for word in words { let mut w = 0; @@ -210,13 +188,9 @@ impl SpiBusRead for MySpi { } *word = w } - - Ok(()) } -} -impl SpiBusWrite for MySpi { - async fn write(&mut self, words: &[u32]) -> Result<(), Self::Error> { + async fn write(&mut self, words: &[u32]) { self.dio.set_as_output(); for word in words { let mut word = *word; @@ -238,6 +212,20 @@ impl SpiBusWrite for MySpi { self.clk.set_low(); self.dio.set_as_input(); - Ok(()) + } +} + +impl cyw43::SpiBusCyw43 for MySpi { + async fn cmd_write(&mut self, write: &[u32]) { + self.cs.set_low(); + self.write(write).await; + self.cs.set_high(); + } + + async fn cmd_read(&mut self, write: u32, read: &mut [u32]) { + self.cs.set_low(); + self.write(slice::from_ref(&write)).await; + self.read(read).await; + self.cs.set_high(); } } diff --git a/examples/rpi-pico-w/src/pio.rs b/examples/rpi-pico-w/src/pio.rs index 1bf304d5..896fd045 100644 --- a/examples/rpi-pico-w/src/pio.rs +++ b/examples/rpi-pico-w/src/pio.rs @@ -2,34 +2,27 @@ use core::slice; use cyw43::SpiBusCyw43; use embassy_rp::dma::Channel; -use embassy_rp::gpio::{Drive, Pin, Pull, SlewRate}; +use embassy_rp::gpio::{Drive, Output, Pin, Pull, SlewRate}; use embassy_rp::pio::{PioStateMachine, ShiftDirection}; use embassy_rp::relocate::RelocatedProgram; use embassy_rp::{pio_instr_util, Peripheral}; -use embedded_hal_1::spi::ErrorType; -use embedded_hal_async::spi::SpiBusFlush; use pio::Wrap; use pio_proc::pio_asm; -pub struct PioSpi { - // cs: Output<'static, AnyPin>, +pub struct PioSpi { + cs: Output<'static, CS>, sm: SM, dma: DMA, wrap_target: u8, } -impl PioSpi +impl PioSpi where SM: PioStateMachine, DMA: Channel, + CS: Pin, { - pub fn new( - mut sm: SM, - // cs: AnyPin, - dio: DIO, - clk: CLK, - dma: DMA, - ) -> Self + pub fn new(mut sm: SM, cs: Output<'static, CS>, dio: DIO, clk: CLK, dma: DMA) -> Self where DIO: Pin, CLK: Pin, @@ -105,7 +98,7 @@ where pio_instr_util::set_pin(&mut sm, 0); Self { - // cs: Output::new(cs, Level::High), + cs, sm, dma, wrap_target: target, @@ -156,43 +149,21 @@ where } } -#[derive(Debug)] -pub enum PioError {} - -impl embedded_hal_async::spi::Error for PioError { - fn kind(&self) -> embedded_hal_1::spi::ErrorKind { - embedded_hal_1::spi::ErrorKind::Other - } -} - -impl ErrorType for PioSpi -where - SM: PioStateMachine, -{ - type Error = PioError; -} - -impl SpiBusFlush for PioSpi -where - SM: PioStateMachine, -{ - async fn flush(&mut self) -> Result<(), Self::Error> { - Ok(()) - } -} - -impl SpiBusCyw43 for PioSpi +impl SpiBusCyw43 for PioSpi where + CS: Pin, SM: PioStateMachine, DMA: Channel, { - async fn cmd_write<'a>(&'a mut self, write: &'a [u32]) -> Result<(), Self::Error> { + async fn cmd_write(&mut self, write: & [u32]) { + self.cs.set_low(); self.write(write).await; - Ok(()) + self.cs.set_high(); } - async fn cmd_read<'a>(&'a mut self, write: &'a [u32], read: &'a mut [u32]) -> Result<(), Self::Error> { - self.cmd_read(write[0], read).await; - Ok(()) + async fn cmd_read(&mut self, write: u32, read: & mut [u32]) { + self.cs.set_low(); + self.cmd_read(write, read).await; + self.cs.set_high(); } } diff --git a/src/bus.rs b/src/bus.rs index e4f9a69b..90990f35 100644 --- a/src/bus.rs +++ b/src/bus.rs @@ -2,22 +2,22 @@ use core::slice; use embassy_time::{Duration, Timer}; use embedded_hal_1::digital::OutputPin; -use embedded_hal_1::spi::ErrorType; -use embedded_hal_async::spi::{transaction, SpiDevice}; use futures::FutureExt; use crate::consts::*; /// Custom Spi Trait that _only_ supports the bus operation of the cyw43 -pub trait SpiBusCyw43: ErrorType { +/// Implementors are expected to hold the CS pin low during an operation. +pub trait SpiBusCyw43 { /// Issues a write command on the bus - /// Frist 32 bits of `word` are expected to be a cmd word - async fn cmd_write<'a>(&'a mut self, write: &'a [Word]) -> Result<(), Self::Error>; + /// First 32 bits of `word` are expected to be a cmd word + async fn cmd_write(&mut self, write: &[u32]); /// Issues a read command on the bus /// `write` is expected to be a 32 bit cmd word /// `read` will contain the response of the device - async fn cmd_read<'a>(&'a mut self, write: &'a [Word], read: &'a mut [Word]) -> Result<(), Self::Error>; + /// + async fn cmd_read(&mut self, write: u32, read: &mut [u32]); } pub(crate) struct Bus { @@ -29,8 +29,7 @@ pub(crate) struct Bus { impl Bus where PWR: OutputPin, - SPI: SpiDevice, - SPI::Bus: SpiBusCyw43, + SPI: SpiBusCyw43, { pub(crate) fn new(pwr: PWR, spi: SPI) -> Self { Self { @@ -87,14 +86,8 @@ where pub async fn wlan_read(&mut self, buf: &mut [u32], len_in_u8: u32) { let cmd = cmd_word(READ, INC_ADDR, FUNC_WLAN, 0, len_in_u8); let len_in_u32 = (len_in_u8 as usize + 3) / 4; - transaction!(&mut self.spi, |bus| async { - // bus.write(&[cmd]).await?; - // bus.read(&mut buf[..len_in_u32]).await?; - bus.cmd_read(slice::from_ref(&cmd), &mut buf[..len_in_u32]).await?; - Ok(()) - }) - .await - .unwrap(); + + self.spi.cmd_read(cmd, &mut buf[..len_in_u32]).await; } pub async fn wlan_write(&mut self, buf: &[u32]) { @@ -104,15 +97,7 @@ where cmd_buf[0] = cmd; cmd_buf[1..][..buf.len()].copy_from_slice(buf); - transaction!(&mut self.spi, |bus| async { - // bus.write(&[cmd]).await?; - // bus.write(buf).await?; - - bus.cmd_write(&cmd_buf).await?; - Ok(()) - }) - .await - .unwrap(); + self.spi.cmd_write(&cmd_buf).await; } #[allow(unused)] @@ -136,22 +121,7 @@ where let cmd = cmd_word(READ, INC_ADDR, FUNC_BACKPLANE, window_offs, len as u32); - transaction!(&mut self.spi, |bus| async { - // bus.write(&[cmd]).await?; - - // // 4-byte response delay. - // let mut junk = [0; 1]; - // bus.read(&mut junk).await?; - - // // Read data - // bus.read(&mut buf[..(len + 3) / 4]).await?; - - bus.cmd_read(slice::from_ref(&cmd), &mut buf[..(len + 3) / 4 + 1]) - .await?; - Ok(()) - }) - .await - .unwrap(); + self.spi.cmd_read(cmd, &mut buf[..(len + 3) / 4 + 1]).await; data[..len].copy_from_slice(&slice8_mut(&mut buf[1..])[..len]); @@ -183,16 +153,7 @@ where let cmd = cmd_word(WRITE, INC_ADDR, FUNC_BACKPLANE, window_offs, len as u32); buf[0] = cmd; - transaction!(&mut self.spi, |bus| async { - // bus.write(&[cmd]).await?; - // bus.write(&buf[..(len + 3) / 4]).await?; - - bus.cmd_write(&buf[..(len + 3) / 4 + 1]).await?; - - Ok(()) - }) - .await - .unwrap(); + self.spi.cmd_write(&buf[..(len + 3) / 4 + 1]).await; // Advance ptr. addr += len as u32; @@ -307,19 +268,7 @@ where let mut buf = [0; 2]; let len = if func == FUNC_BACKPLANE { 2 } else { 1 }; - transaction!(&mut self.spi, |bus| async { - // bus.write(&[cmd]).await?; - // if func == FUNC_BACKPLANE { - // // 4-byte response delay. - // bus.read(&mut buf).await?; - // } - // bus.read(&mut buf).await?; - - bus.cmd_read(slice::from_ref(&cmd), &mut buf[..len]).await?; - Ok(()) - }) - .await - .unwrap(); + self.spi.cmd_read(cmd, &mut buf[..len]).await; if func == FUNC_BACKPLANE { buf[1] @@ -331,13 +280,7 @@ where async fn writen(&mut self, func: u32, addr: u32, val: u32, len: u32) { let cmd = cmd_word(WRITE, INC_ADDR, func, addr, len); - transaction!(&mut self.spi, |bus| async { - // bus.write(&[cmd, val]).await?; - bus.cmd_write(&[cmd, val]).await?; - Ok(()) - }) - .await - .unwrap(); + self.spi.cmd_write(&[cmd, val]).await; } async fn read32_swapped(&mut self, addr: u32) -> u32 { @@ -345,15 +288,7 @@ where let cmd = swap16(cmd); let mut buf = [0; 1]; - transaction!(&mut self.spi, |bus| async { - // bus.write(&[swap16(cmd)]).await?; - // bus.read(&mut buf).await?; - - bus.cmd_read(slice::from_ref(&cmd), &mut buf).await?; - Ok(()) - }) - .await - .unwrap(); + self.spi.cmd_read(cmd, &mut buf).await; swap16(buf[0]) } @@ -362,14 +297,7 @@ where let cmd = cmd_word(WRITE, INC_ADDR, FUNC_BUS, addr, 4); let buf = [swap16(cmd), swap16(val)]; - transaction!(&mut self.spi, |bus| async { - // bus.write(&[swap16(cmd), swap16(val)]).await?; - - bus.cmd_write(&buf).await?; - Ok(()) - }) - .await - .unwrap(); + self.spi.cmd_write(&buf).await; } } diff --git a/src/lib.rs b/src/lib.rs index bcc3c59b..f0a7aaa0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -22,7 +22,6 @@ use embassy_futures::yield_now; use embassy_net_driver_channel as ch; use embassy_time::{block_for, Duration, Timer}; use embedded_hal_1::digital::OutputPin; -use embedded_hal_async::spi::SpiDevice; use crate::bus::Bus; pub use crate::bus::SpiBusCyw43; @@ -513,8 +512,7 @@ pub async fn new<'a, PWR, SPI>( ) -> (NetDriver<'a>, Control<'a>, Runner<'a, PWR, SPI>) where PWR: OutputPin, - SPI: SpiDevice, - SPI::Bus: SpiBusCyw43, + SPI: SpiBusCyw43, { let (ch_runner, device) = ch::new(&mut state.ch, [0; 6]); let state_ch = ch_runner.state_runner(); @@ -552,8 +550,7 @@ where impl<'a, PWR, SPI> Runner<'a, PWR, SPI> where PWR: OutputPin, - SPI: SpiDevice, - SPI::Bus: SpiBusCyw43, + SPI: SpiBusCyw43, { async fn init(&mut self, firmware: &[u8]) { self.bus.init().await; From 3034e8fb458cae0ff84d1ca07b4a64bced815f0c Mon Sep 17 00:00:00 2001 From: kbleeke Date: Tue, 21 Mar 2023 19:26:01 +0100 Subject: [PATCH 069/178] document response delay quirks in bus code --- src/bus.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/bus.rs b/src/bus.rs index 90990f35..262b9e0d 100644 --- a/src/bus.rs +++ b/src/bus.rs @@ -16,7 +16,8 @@ pub trait SpiBusCyw43 { /// Issues a read command on the bus /// `write` is expected to be a 32 bit cmd word /// `read` will contain the response of the device - /// + /// Backplane reads have a response delay that produces one extra unspecified word at the beginning of `read`. + /// Callers that want to read `n` word from the backplane, have to provide a slice that is `n+1` words long. async fn cmd_read(&mut self, write: u32, read: &mut [u32]); } @@ -108,6 +109,7 @@ where // To simplify, enforce 4-align for now. assert!(addr % 4 == 0); + // Backplane read buffer has one extra word for the response delay. let mut buf = [0u32; BACKPLANE_MAX_TRANSFER_SIZE / 4 + 1]; while !data.is_empty() { @@ -121,8 +123,10 @@ where let cmd = cmd_word(READ, INC_ADDR, FUNC_BACKPLANE, window_offs, len as u32); + // round `buf` to word boundary, add one extra word for the response delay self.spi.cmd_read(cmd, &mut buf[..(len + 3) / 4 + 1]).await; + // when writing out the data, we skip the response-delay byte data[..len].copy_from_slice(&slice8_mut(&mut buf[1..])[..len]); // Advance ptr. @@ -266,10 +270,12 @@ where async fn readn(&mut self, func: u32, addr: u32, len: u32) -> u32 { let cmd = cmd_word(READ, INC_ADDR, func, addr, len); let mut buf = [0; 2]; + // if we are reading from the backplane, we need an extra word for the response delay let len = if func == FUNC_BACKPLANE { 2 } else { 1 }; self.spi.cmd_read(cmd, &mut buf[..len]).await; + // if we read from the backplane, the result is in the second word, after the response delay if func == FUNC_BACKPLANE { buf[1] } else { From f82f931dc2b8df2338fb8331ad27d667811e5c09 Mon Sep 17 00:00:00 2001 From: kbleeke Date: Tue, 21 Mar 2023 19:30:45 +0100 Subject: [PATCH 070/178] revert formatting changes in Cargo.toml --- examples/rpi-pico-w/Cargo.toml | 34 +++++----------------------------- 1 file changed, 5 insertions(+), 29 deletions(-) diff --git a/examples/rpi-pico-w/Cargo.toml b/examples/rpi-pico-w/Cargo.toml index 17b4214d..4a531c88 100644 --- a/examples/rpi-pico-w/Cargo.toml +++ b/examples/rpi-pico-w/Cargo.toml @@ -6,30 +6,10 @@ edition = "2021" [dependencies] cyw43 = { path = "../../", features = ["defmt", "firmware-logs"] } -embassy-executor = { version = "0.1.0", features = [ - "defmt", - "integrated-timers", -] } -embassy-time = { version = "0.1.0", features = [ - "defmt", - "defmt-timestamp-uptime", -] } -embassy-rp = { version = "0.1.0", features = [ - "defmt", - "unstable-traits", - "nightly", - "unstable-pac", - "pio", - "time-driver", -] } -embassy-net = { version = "0.1.0", features = [ - "defmt", - "tcp", - "dhcpv4", - "medium-ethernet", - "unstable-traits", - "nightly", -] } +embassy-executor = { version = "0.1.0", features = ["defmt", "integrated-timers"] } +embassy-time = { version = "0.1.0", features = ["defmt", "defmt-timestamp-uptime"] } +embassy-rp = { version = "0.1.0", features = ["defmt", "unstable-traits", "nightly", "unstable-pac", "time-driver", "pio"] } +embassy-net = { version = "0.1.0", features = ["defmt", "tcp", "dhcpv4", "medium-ethernet", "unstable-traits", "nightly"] } atomic-polyfill = "0.1.5" static_cell = "1.0" @@ -39,11 +19,7 @@ panic-probe = { version = "0.3", features = ["print-defmt"] } cortex-m = { version = "0.7.6", features = ["critical-section-single-core"] } cortex-m-rt = "0.7.0" -futures = { version = "0.3.17", default-features = false, features = [ - "async-await", - "cfg-target-has-atomic", - "unstable", -] } +futures = { version = "0.3.17", default-features = false, features = ["async-await", "cfg-target-has-atomic", "unstable"] } pio-proc = "0.2" pio = "0.2.1" From 359b1c7fdb246c125e0b835eb58283a8a9a6a946 Mon Sep 17 00:00:00 2001 From: kbleeke Date: Tue, 21 Mar 2023 19:39:41 +0100 Subject: [PATCH 071/178] replace inspect() with direct calls to trace!() after awaiting --- examples/rpi-pico-w/src/pio.rs | 4 ++-- src/bus.rs | 28 ++++++++++------------------ 2 files changed, 12 insertions(+), 20 deletions(-) diff --git a/examples/rpi-pico-w/src/pio.rs b/examples/rpi-pico-w/src/pio.rs index 896fd045..8017f4f4 100644 --- a/examples/rpi-pico-w/src/pio.rs +++ b/examples/rpi-pico-w/src/pio.rs @@ -155,13 +155,13 @@ where SM: PioStateMachine, DMA: Channel, { - async fn cmd_write(&mut self, write: & [u32]) { + async fn cmd_write(&mut self, write: &[u32]) { self.cs.set_low(); self.write(write).await; self.cs.set_high(); } - async fn cmd_read(&mut self, write: u32, read: & mut [u32]) { + async fn cmd_read(&mut self, write: u32, read: &mut [u32]) { self.cs.set_low(); self.cmd_read(write, read).await; self.cs.set_high(); diff --git a/src/bus.rs b/src/bus.rs index 262b9e0d..f77b890d 100644 --- a/src/bus.rs +++ b/src/bus.rs @@ -55,32 +55,24 @@ where {} self.write32_swapped(REG_BUS_TEST_RW, TEST_PATTERN).await; - let val = self - .read32_swapped(REG_BUS_TEST_RW) - .inspect(|v| defmt::trace!("{:#x}", v)) - .await; + let val = self.read32_swapped(REG_BUS_TEST_RW).await; + defmt::trace!("{:#x}", val); assert_eq!(val, TEST_PATTERN); - self.read32_swapped(REG_BUS_CTRL) - .inspect(|v| defmt::trace!("{:#010b}", (v & 0xff))) - .await; + let val = self.read32_swapped(REG_BUS_CTRL).await; + defmt::trace!("{:#010b}", (val & 0xff)); // 32-bit word length, little endian (which is the default endianess). self.write32_swapped(REG_BUS_CTRL, WORD_LENGTH_32 | HIGH_SPEED).await; - self.read8(FUNC_BUS, REG_BUS_CTRL) - .inspect(|v| defmt::trace!("{:#b}", v)) - .await; + let val = self.read8(FUNC_BUS, REG_BUS_CTRL).await; + defmt::trace!("{:#b}", val); - let val = self - .read32(FUNC_BUS, REG_BUS_TEST_RO) - .inspect(|v| defmt::trace!("{:#x}", v)) - .await; + let val = self.read32(FUNC_BUS, REG_BUS_TEST_RO).await; + defmt::trace!("{:#x}", val); assert_eq!(val, FEEDBEAD); - let val = self - .read32(FUNC_BUS, REG_BUS_TEST_RW) - .inspect(|v| defmt::trace!("{:#x}", v)) - .await; + let val = self.read32(FUNC_BUS, REG_BUS_TEST_RW).await; + defmt::trace!("{:#x}", val); assert_eq!(val, TEST_PATTERN); } From 369f2059627c579c344b1f4d8d34002b466e057d Mon Sep 17 00:00:00 2001 From: kbleeke Date: Wed, 22 Mar 2023 11:33:55 +0100 Subject: [PATCH 072/178] wifi task needs to be spawned immediately, otherwise ioctls are just stuck (duh). fix #44 --- examples/rpi-pico-w/src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/rpi-pico-w/src/main.rs b/examples/rpi-pico-w/src/main.rs index 67348e45..43485137 100644 --- a/examples/rpi-pico-w/src/main.rs +++ b/examples/rpi-pico-w/src/main.rs @@ -77,6 +77,7 @@ async fn main(spawner: Spawner) { let state = singleton!(cyw43::State::new()); let (net_device, mut control, runner) = cyw43::new(state, pwr, spi, fw).await; + unwrap!(spawner.spawn(wifi_task(runner))); control.init(clm).await; control @@ -101,7 +102,6 @@ async fn main(spawner: Spawner) { seed )); - unwrap!(spawner.spawn(wifi_task(runner))); unwrap!(spawner.spawn(net_task(stack))); //control.join_open(env!("WIFI_NETWORK")).await; From 20923080e6ea313278b5f3aa8ec21055c6208527 Mon Sep 17 00:00:00 2001 From: kbleeke Date: Thu, 2 Mar 2023 12:10:13 +0100 Subject: [PATCH 073/178] split lib.rs into multiple files --- src/control.rs | 299 +++++++++++++++++ src/lib.rs | 890 +------------------------------------------------ src/nvram.rs | 54 +++ src/runner.rs | 564 +++++++++++++++++++++++++++++++ 4 files changed, 926 insertions(+), 881 deletions(-) create mode 100644 src/control.rs create mode 100644 src/nvram.rs create mode 100644 src/runner.rs diff --git a/src/control.rs b/src/control.rs new file mode 100644 index 00000000..7f1c9fe8 --- /dev/null +++ b/src/control.rs @@ -0,0 +1,299 @@ +use core::cell::Cell; +use core::cmp::{max, min}; + +use ch::driver::LinkState; +use embassy_futures::yield_now; +use embassy_net_driver_channel as ch; +use embassy_time::{Duration, Timer}; + +pub use crate::bus::SpiBusCyw43; +use crate::consts::*; +use crate::events::{Event, EventQueue}; +use crate::structs::*; +use crate::{countries, IoctlState, IoctlType, PowerManagementMode}; + +pub struct Control<'a> { + state_ch: ch::StateRunner<'a>, + event_sub: &'a EventQueue, + ioctl_state: &'a Cell, +} + +impl<'a> Control<'a> { + pub(crate) fn new( + state_ch: ch::StateRunner<'a>, + event_sub: &'a EventQueue, + ioctl_state: &'a Cell, + ) -> Self { + Self { + state_ch, + event_sub, + ioctl_state, + } + } + + pub async fn init(&mut self, clm: &[u8]) { + const CHUNK_SIZE: usize = 1024; + + info!("Downloading CLM..."); + + let mut offs = 0; + for chunk in clm.chunks(CHUNK_SIZE) { + let mut flag = DOWNLOAD_FLAG_HANDLER_VER; + if offs == 0 { + flag |= DOWNLOAD_FLAG_BEGIN; + } + offs += chunk.len(); + if offs == clm.len() { + flag |= DOWNLOAD_FLAG_END; + } + + let header = DownloadHeader { + flag, + dload_type: DOWNLOAD_TYPE_CLM, + len: chunk.len() as _, + crc: 0, + }; + let mut buf = [0; 8 + 12 + CHUNK_SIZE]; + buf[0..8].copy_from_slice(b"clmload\x00"); + buf[8..20].copy_from_slice(&header.to_bytes()); + buf[20..][..chunk.len()].copy_from_slice(&chunk); + self.ioctl(IoctlType::Set, IOCTL_CMD_SET_VAR, 0, &mut buf[..8 + 12 + chunk.len()]) + .await; + } + + // check clmload ok + assert_eq!(self.get_iovar_u32("clmload_status").await, 0); + + info!("Configuring misc stuff..."); + + // Disable tx gloming which transfers multiple packets in one request. + // 'glom' is short for "conglomerate" which means "gather together into + // a compact mass". + self.set_iovar_u32("bus:txglom", 0).await; + self.set_iovar_u32("apsta", 1).await; + + // read MAC addr. + let mut mac_addr = [0; 6]; + assert_eq!(self.get_iovar("cur_etheraddr", &mut mac_addr).await, 6); + info!("mac addr: {:02x}", mac_addr); + + let country = countries::WORLD_WIDE_XX; + let country_info = CountryInfo { + country_abbrev: [country.code[0], country.code[1], 0, 0], + country_code: [country.code[0], country.code[1], 0, 0], + rev: if country.rev == 0 { -1 } else { country.rev as _ }, + }; + self.set_iovar("country", &country_info.to_bytes()).await; + + // set country takes some time, next ioctls fail if we don't wait. + Timer::after(Duration::from_millis(100)).await; + + // Set antenna to chip antenna + self.ioctl_set_u32(IOCTL_CMD_ANTDIV, 0, 0).await; + + self.set_iovar_u32("bus:txglom", 0).await; + Timer::after(Duration::from_millis(100)).await; + //self.set_iovar_u32("apsta", 1).await; // this crashes, also we already did it before...?? + //Timer::after(Duration::from_millis(100)).await; + self.set_iovar_u32("ampdu_ba_wsize", 8).await; + Timer::after(Duration::from_millis(100)).await; + self.set_iovar_u32("ampdu_mpdu", 4).await; + Timer::after(Duration::from_millis(100)).await; + //self.set_iovar_u32("ampdu_rx_factor", 0).await; // this crashes + + //Timer::after(Duration::from_millis(100)).await; + + // evts + let mut evts = EventMask { + iface: 0, + events: [0xFF; 24], + }; + + // Disable spammy uninteresting events. + evts.unset(Event::RADIO); + evts.unset(Event::IF); + evts.unset(Event::PROBREQ_MSG); + evts.unset(Event::PROBREQ_MSG_RX); + evts.unset(Event::PROBRESP_MSG); + evts.unset(Event::PROBRESP_MSG); + evts.unset(Event::ROAM); + + self.set_iovar("bsscfg:event_msgs", &evts.to_bytes()).await; + + Timer::after(Duration::from_millis(100)).await; + + // set wifi up + self.ioctl(IoctlType::Set, IOCTL_CMD_UP, 0, &mut []).await; + + Timer::after(Duration::from_millis(100)).await; + + self.ioctl_set_u32(110, 0, 1).await; // SET_GMODE = auto + self.ioctl_set_u32(142, 0, 0).await; // SET_BAND = any + + Timer::after(Duration::from_millis(100)).await; + + self.state_ch.set_ethernet_address(mac_addr); + self.state_ch.set_link_state(LinkState::Up); // TODO do on join/leave + + info!("INIT DONE"); + } + + pub async fn set_power_management(&mut self, mode: PowerManagementMode) { + // power save mode + let mode_num = mode.mode(); + if mode_num == 2 { + self.set_iovar_u32("pm2_sleep_ret", mode.sleep_ret_ms() as u32).await; + self.set_iovar_u32("bcn_li_bcn", mode.beacon_period() as u32).await; + self.set_iovar_u32("bcn_li_dtim", mode.dtim_period() as u32).await; + self.set_iovar_u32("assoc_listen", mode.assoc() as u32).await; + } + self.ioctl_set_u32(86, 0, mode_num).await; + } + + pub async fn join_open(&mut self, ssid: &str) { + self.set_iovar_u32("ampdu_ba_wsize", 8).await; + + self.ioctl_set_u32(134, 0, 0).await; // wsec = open + self.set_iovar_u32x2("bsscfg:sup_wpa", 0, 0).await; + self.ioctl_set_u32(20, 0, 1).await; // set_infra = 1 + self.ioctl_set_u32(22, 0, 0).await; // set_auth = open (0) + + let mut i = SsidInfo { + len: ssid.len() as _, + ssid: [0; 32], + }; + i.ssid[..ssid.len()].copy_from_slice(ssid.as_bytes()); + self.ioctl(IoctlType::Set, IOCTL_CMD_SET_SSID, 0, &mut i.to_bytes()) + .await; // set_ssid + + info!("JOINED"); + } + + pub async fn join_wpa2(&mut self, ssid: &str, passphrase: &str) { + self.set_iovar_u32("ampdu_ba_wsize", 8).await; + + self.ioctl_set_u32(134, 0, 4).await; // wsec = wpa2 + self.set_iovar_u32x2("bsscfg:sup_wpa", 0, 1).await; + self.set_iovar_u32x2("bsscfg:sup_wpa2_eapver", 0, 0xFFFF_FFFF).await; + self.set_iovar_u32x2("bsscfg:sup_wpa_tmo", 0, 2500).await; + + Timer::after(Duration::from_millis(100)).await; + + let mut pfi = PassphraseInfo { + len: passphrase.len() as _, + flags: 1, + passphrase: [0; 64], + }; + pfi.passphrase[..passphrase.len()].copy_from_slice(passphrase.as_bytes()); + self.ioctl(IoctlType::Set, IOCTL_CMD_SET_PASSPHRASE, 0, &mut pfi.to_bytes()) + .await; // WLC_SET_WSEC_PMK + + self.ioctl_set_u32(20, 0, 1).await; // set_infra = 1 + self.ioctl_set_u32(22, 0, 0).await; // set_auth = 0 (open) + self.ioctl_set_u32(165, 0, 0x80).await; // set_wpa_auth + + let mut i = SsidInfo { + len: ssid.len() as _, + ssid: [0; 32], + }; + i.ssid[..ssid.len()].copy_from_slice(ssid.as_bytes()); + + let mut subscriber = self.event_sub.subscriber().unwrap(); + self.ioctl(IoctlType::Set, 26, 0, &mut i.to_bytes()).await; // set_ssid + + loop { + let msg = subscriber.next_message_pure().await; + if msg.event_type == Event::AUTH && msg.status != 0 { + // retry + defmt::warn!("JOIN failed with status={}", msg.status); + self.ioctl(IoctlType::Set, 26, 0, &mut i.to_bytes()).await; + } else if msg.event_type == Event::JOIN && msg.status == 0 { + // successful join + break; + } + } + + info!("JOINED"); + } + + pub async fn gpio_set(&mut self, gpio_n: u8, gpio_en: bool) { + assert!(gpio_n < 3); + self.set_iovar_u32x2("gpioout", 1 << gpio_n, if gpio_en { 1 << gpio_n } else { 0 }) + .await + } + + async fn set_iovar_u32x2(&mut self, name: &str, val1: u32, val2: u32) { + let mut buf = [0; 8]; + buf[0..4].copy_from_slice(&val1.to_le_bytes()); + buf[4..8].copy_from_slice(&val2.to_le_bytes()); + self.set_iovar(name, &buf).await + } + + async fn set_iovar_u32(&mut self, name: &str, val: u32) { + self.set_iovar(name, &val.to_le_bytes()).await + } + + async fn get_iovar_u32(&mut self, name: &str) -> u32 { + let mut buf = [0; 4]; + let len = self.get_iovar(name, &mut buf).await; + assert_eq!(len, 4); + u32::from_le_bytes(buf) + } + + async fn set_iovar(&mut self, name: &str, val: &[u8]) { + info!("set {} = {:02x}", name, val); + + let mut buf = [0; 64]; + buf[..name.len()].copy_from_slice(name.as_bytes()); + buf[name.len()] = 0; + buf[name.len() + 1..][..val.len()].copy_from_slice(val); + + let total_len = name.len() + 1 + val.len(); + self.ioctl(IoctlType::Set, IOCTL_CMD_SET_VAR, 0, &mut buf[..total_len]) + .await; + } + + // TODO this is not really working, it always returns all zeros. + async fn get_iovar(&mut self, name: &str, res: &mut [u8]) -> usize { + info!("get {}", name); + + let mut buf = [0; 64]; + buf[..name.len()].copy_from_slice(name.as_bytes()); + buf[name.len()] = 0; + + let total_len = max(name.len() + 1, res.len()); + let res_len = self + .ioctl(IoctlType::Get, IOCTL_CMD_GET_VAR, 0, &mut buf[..total_len]) + .await; + + let out_len = min(res.len(), res_len); + res[..out_len].copy_from_slice(&buf[..out_len]); + out_len + } + + async fn ioctl_set_u32(&mut self, cmd: u32, iface: u32, val: u32) { + let mut buf = val.to_le_bytes(); + self.ioctl(IoctlType::Set, cmd, iface, &mut buf).await; + } + + async fn ioctl(&mut self, kind: IoctlType, cmd: u32, iface: u32, buf: &mut [u8]) -> usize { + // TODO cancel ioctl on future drop. + + while !matches!(self.ioctl_state.get(), IoctlState::Idle) { + yield_now().await; + } + + self.ioctl_state.set(IoctlState::Pending { kind, cmd, iface, buf }); + + let resp_len = loop { + if let IoctlState::Done { resp_len } = self.ioctl_state.get() { + break resp_len; + } + yield_now().await; + }; + + self.ioctl_state.set(IoctlState::Idle); + + resp_len + } +} diff --git a/src/lib.rs b/src/lib.rs index 1b7d603d..af8f74a6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -13,23 +13,20 @@ mod countries; mod events; mod structs; -use core::cell::Cell; -use core::cmp::{max, min}; -use core::slice; +mod control; +mod nvram; +mod runner; + +use core::cell::Cell; -use ch::driver::LinkState; -use embassy_futures::yield_now; use embassy_net_driver_channel as ch; -use embassy_sync::pubsub::PubSubBehavior; -use embassy_time::{block_for, Duration, Timer}; use embedded_hal_1::digital::OutputPin; use events::EventQueue; use crate::bus::Bus; pub use crate::bus::SpiBusCyw43; -use crate::consts::*; -use crate::events::{Event, EventStatus}; -use crate::structs::*; +pub use crate::control::Control; +pub use crate::runner::Runner; const MTU: usize = 1514; @@ -143,12 +140,6 @@ impl State { } } -pub struct Control<'a> { - state_ch: ch::StateRunner<'a>, - event_sub: &'a EventQueue, - ioctl_state: &'a Cell, -} - #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum PowerManagementMode { /// Custom, officially unsupported mode. Use at your own risk. @@ -233,297 +224,6 @@ impl PowerManagementMode { } } -impl<'a> Control<'a> { - pub async fn init(&mut self, clm: &[u8]) { - const CHUNK_SIZE: usize = 1024; - - info!("Downloading CLM..."); - - let mut offs = 0; - for chunk in clm.chunks(CHUNK_SIZE) { - let mut flag = DOWNLOAD_FLAG_HANDLER_VER; - if offs == 0 { - flag |= DOWNLOAD_FLAG_BEGIN; - } - offs += chunk.len(); - if offs == clm.len() { - flag |= DOWNLOAD_FLAG_END; - } - - let header = DownloadHeader { - flag, - dload_type: DOWNLOAD_TYPE_CLM, - len: chunk.len() as _, - crc: 0, - }; - let mut buf = [0; 8 + 12 + CHUNK_SIZE]; - buf[0..8].copy_from_slice(b"clmload\x00"); - buf[8..20].copy_from_slice(&header.to_bytes()); - buf[20..][..chunk.len()].copy_from_slice(&chunk); - self.ioctl(IoctlType::Set, IOCTL_CMD_SET_VAR, 0, &mut buf[..8 + 12 + chunk.len()]) - .await; - } - - // check clmload ok - assert_eq!(self.get_iovar_u32("clmload_status").await, 0); - - info!("Configuring misc stuff..."); - - // Disable tx gloming which transfers multiple packets in one request. - // 'glom' is short for "conglomerate" which means "gather together into - // a compact mass". - self.set_iovar_u32("bus:txglom", 0).await; - self.set_iovar_u32("apsta", 1).await; - - // read MAC addr. - let mut mac_addr = [0; 6]; - assert_eq!(self.get_iovar("cur_etheraddr", &mut mac_addr).await, 6); - info!("mac addr: {:02x}", mac_addr); - - let country = countries::WORLD_WIDE_XX; - let country_info = CountryInfo { - country_abbrev: [country.code[0], country.code[1], 0, 0], - country_code: [country.code[0], country.code[1], 0, 0], - rev: if country.rev == 0 { -1 } else { country.rev as _ }, - }; - self.set_iovar("country", &country_info.to_bytes()).await; - - // set country takes some time, next ioctls fail if we don't wait. - Timer::after(Duration::from_millis(100)).await; - - // Set antenna to chip antenna - self.ioctl_set_u32(IOCTL_CMD_ANTDIV, 0, 0).await; - - self.set_iovar_u32("bus:txglom", 0).await; - Timer::after(Duration::from_millis(100)).await; - //self.set_iovar_u32("apsta", 1).await; // this crashes, also we already did it before...?? - //Timer::after(Duration::from_millis(100)).await; - self.set_iovar_u32("ampdu_ba_wsize", 8).await; - Timer::after(Duration::from_millis(100)).await; - self.set_iovar_u32("ampdu_mpdu", 4).await; - Timer::after(Duration::from_millis(100)).await; - //self.set_iovar_u32("ampdu_rx_factor", 0).await; // this crashes - - //Timer::after(Duration::from_millis(100)).await; - - // evts - let mut evts = EventMask { - iface: 0, - events: [0xFF; 24], - }; - - // Disable spammy uninteresting events. - evts.unset(Event::RADIO); - evts.unset(Event::IF); - evts.unset(Event::PROBREQ_MSG); - evts.unset(Event::PROBREQ_MSG_RX); - evts.unset(Event::PROBRESP_MSG); - evts.unset(Event::PROBRESP_MSG); - evts.unset(Event::ROAM); - - self.set_iovar("bsscfg:event_msgs", &evts.to_bytes()).await; - - Timer::after(Duration::from_millis(100)).await; - - // set wifi up - self.ioctl(IoctlType::Set, IOCTL_CMD_UP, 0, &mut []).await; - - Timer::after(Duration::from_millis(100)).await; - - self.ioctl_set_u32(110, 0, 1).await; // SET_GMODE = auto - self.ioctl_set_u32(142, 0, 0).await; // SET_BAND = any - - Timer::after(Duration::from_millis(100)).await; - - self.state_ch.set_ethernet_address(mac_addr); - self.state_ch.set_link_state(LinkState::Up); // TODO do on join/leave - - info!("INIT DONE"); - } - - pub async fn set_power_management(&mut self, mode: PowerManagementMode) { - // power save mode - let mode_num = mode.mode(); - if mode_num == 2 { - self.set_iovar_u32("pm2_sleep_ret", mode.sleep_ret_ms() as u32).await; - self.set_iovar_u32("bcn_li_bcn", mode.beacon_period() as u32).await; - self.set_iovar_u32("bcn_li_dtim", mode.dtim_period() as u32).await; - self.set_iovar_u32("assoc_listen", mode.assoc() as u32).await; - } - self.ioctl_set_u32(86, 0, mode_num).await; - } - - pub async fn join_open(&mut self, ssid: &str) { - self.set_iovar_u32("ampdu_ba_wsize", 8).await; - - self.ioctl_set_u32(134, 0, 0).await; // wsec = open - self.set_iovar_u32x2("bsscfg:sup_wpa", 0, 0).await; - self.ioctl_set_u32(20, 0, 1).await; // set_infra = 1 - self.ioctl_set_u32(22, 0, 0).await; // set_auth = open (0) - - let mut i = SsidInfo { - len: ssid.len() as _, - ssid: [0; 32], - }; - i.ssid[..ssid.len()].copy_from_slice(ssid.as_bytes()); - self.ioctl(IoctlType::Set, IOCTL_CMD_SET_SSID, 0, &mut i.to_bytes()) - .await; // set_ssid - - info!("JOINED"); - } - - pub async fn join_wpa2(&mut self, ssid: &str, passphrase: &str) { - self.set_iovar_u32("ampdu_ba_wsize", 8).await; - - self.ioctl_set_u32(134, 0, 4).await; // wsec = wpa2 - self.set_iovar_u32x2("bsscfg:sup_wpa", 0, 1).await; - self.set_iovar_u32x2("bsscfg:sup_wpa2_eapver", 0, 0xFFFF_FFFF).await; - self.set_iovar_u32x2("bsscfg:sup_wpa_tmo", 0, 2500).await; - - Timer::after(Duration::from_millis(100)).await; - - let mut pfi = PassphraseInfo { - len: passphrase.len() as _, - flags: 1, - passphrase: [0; 64], - }; - pfi.passphrase[..passphrase.len()].copy_from_slice(passphrase.as_bytes()); - self.ioctl(IoctlType::Set, IOCTL_CMD_SET_PASSPHRASE, 0, &mut pfi.to_bytes()) - .await; // WLC_SET_WSEC_PMK - - self.ioctl_set_u32(20, 0, 1).await; // set_infra = 1 - self.ioctl_set_u32(22, 0, 0).await; // set_auth = 0 (open) - self.ioctl_set_u32(165, 0, 0x80).await; // set_wpa_auth - - let mut i = SsidInfo { - len: ssid.len() as _, - ssid: [0; 32], - }; - i.ssid[..ssid.len()].copy_from_slice(ssid.as_bytes()); - - let mut subscriber = self.event_sub.subscriber().unwrap(); - self.ioctl(IoctlType::Set, 26, 0, &mut i.to_bytes()).await; // set_ssid - - loop { - let msg = subscriber.next_message_pure().await; - if msg.event_type == Event::AUTH && msg.status != 0 { - // retry - defmt::warn!("JOIN failed with status={}", msg.status); - self.ioctl(IoctlType::Set, 26, 0, &mut i.to_bytes()).await; - } else if msg.event_type == Event::JOIN && msg.status == 0 { - // successful join - break; - } - } - - info!("JOINED"); - } - - pub async fn gpio_set(&mut self, gpio_n: u8, gpio_en: bool) { - assert!(gpio_n < 3); - self.set_iovar_u32x2("gpioout", 1 << gpio_n, if gpio_en { 1 << gpio_n } else { 0 }) - .await - } - - async fn set_iovar_u32x2(&mut self, name: &str, val1: u32, val2: u32) { - let mut buf = [0; 8]; - buf[0..4].copy_from_slice(&val1.to_le_bytes()); - buf[4..8].copy_from_slice(&val2.to_le_bytes()); - self.set_iovar(name, &buf).await - } - - async fn set_iovar_u32(&mut self, name: &str, val: u32) { - self.set_iovar(name, &val.to_le_bytes()).await - } - - async fn get_iovar_u32(&mut self, name: &str) -> u32 { - let mut buf = [0; 4]; - let len = self.get_iovar(name, &mut buf).await; - assert_eq!(len, 4); - u32::from_le_bytes(buf) - } - - async fn set_iovar(&mut self, name: &str, val: &[u8]) { - info!("set {} = {:02x}", name, val); - - let mut buf = [0; 64]; - buf[..name.len()].copy_from_slice(name.as_bytes()); - buf[name.len()] = 0; - buf[name.len() + 1..][..val.len()].copy_from_slice(val); - - let total_len = name.len() + 1 + val.len(); - self.ioctl(IoctlType::Set, IOCTL_CMD_SET_VAR, 0, &mut buf[..total_len]) - .await; - } - - // TODO this is not really working, it always returns all zeros. - async fn get_iovar(&mut self, name: &str, res: &mut [u8]) -> usize { - info!("get {}", name); - - let mut buf = [0; 64]; - buf[..name.len()].copy_from_slice(name.as_bytes()); - buf[name.len()] = 0; - - let total_len = max(name.len() + 1, res.len()); - let res_len = self - .ioctl(IoctlType::Get, IOCTL_CMD_GET_VAR, 0, &mut buf[..total_len]) - .await; - - let out_len = min(res.len(), res_len); - res[..out_len].copy_from_slice(&buf[..out_len]); - out_len - } - - async fn ioctl_set_u32(&mut self, cmd: u32, iface: u32, val: u32) { - let mut buf = val.to_le_bytes(); - self.ioctl(IoctlType::Set, cmd, iface, &mut buf).await; - } - - async fn ioctl(&mut self, kind: IoctlType, cmd: u32, iface: u32, buf: &mut [u8]) -> usize { - // TODO cancel ioctl on future drop. - - while !matches!(self.ioctl_state.get(), IoctlState::Idle) { - yield_now().await; - } - - self.ioctl_state.set(IoctlState::Pending { kind, cmd, iface, buf }); - - let resp_len = loop { - if let IoctlState::Done { resp_len } = self.ioctl_state.get() { - break resp_len; - } - yield_now().await; - }; - - self.ioctl_state.set(IoctlState::Idle); - - resp_len - } -} - -pub struct Runner<'a, PWR, SPI> { - ch: ch::Runner<'a, MTU>, - bus: Bus, - - ioctl_state: &'a Cell, - ioctl_id: u16, - sdpcm_seq: u8, - sdpcm_seq_max: u8, - - events: &'a EventQueue, - - #[cfg(feature = "firmware-logs")] - log: LogState, -} - -#[cfg(feature = "firmware-logs")] -struct LogState { - addr: u32, - last_idx: usize, - buf: [u8; 256], - buf_count: usize, -} - pub type NetDriver<'a> = ch::Device<'a, MTU>; pub async fn new<'a, PWR, SPI>( @@ -539,585 +239,13 @@ where 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, - bus: Bus::new(pwr, spi), - - ioctl_state: &state.ioctl_state, - ioctl_id: 0, - sdpcm_seq: 0, - sdpcm_seq_max: 1, - - events: &state.events, - - #[cfg(feature = "firmware-logs")] - log: LogState { - addr: 0, - last_idx: 0, - buf: [0; 256], - buf_count: 0, - }, - }; + let mut runner = Runner::new(ch_runner, Bus::new(pwr, spi), &state.ioctl_state, &state.events); runner.init(firmware).await; ( device, - Control { - state_ch, - event_sub: &&state.events, - ioctl_state: &state.ioctl_state, - }, + Control::new(state_ch, &state.events, &state.ioctl_state), runner, ) } - -impl<'a, PWR, SPI> Runner<'a, PWR, SPI> -where - PWR: OutputPin, - SPI: SpiBusCyw43, -{ - async fn init(&mut self, firmware: &[u8]) { - self.bus.init().await; - - // Init ALP (Active Low Power) clock - self.bus - .write8(FUNC_BACKPLANE, REG_BACKPLANE_CHIP_CLOCK_CSR, BACKPLANE_ALP_AVAIL_REQ) - .await; - info!("waiting for clock..."); - while self.bus.read8(FUNC_BACKPLANE, REG_BACKPLANE_CHIP_CLOCK_CSR).await & BACKPLANE_ALP_AVAIL == 0 {} - info!("clock ok"); - - let chip_id = self.bus.bp_read16(0x1800_0000).await; - info!("chip ID: {}", chip_id); - - // Upload firmware. - self.core_disable(Core::WLAN).await; - self.core_reset(Core::SOCSRAM).await; - self.bus.bp_write32(CHIP.socsram_base_address + 0x10, 3).await; - self.bus.bp_write32(CHIP.socsram_base_address + 0x44, 0).await; - - let ram_addr = CHIP.atcm_ram_base_address; - - info!("loading fw"); - self.bus.bp_write(ram_addr, firmware).await; - - info!("loading nvram"); - // Round up to 4 bytes. - let nvram_len = (NVRAM.len() + 3) / 4 * 4; - self.bus - .bp_write(ram_addr + CHIP.chip_ram_size - 4 - nvram_len as u32, NVRAM) - .await; - - let nvram_len_words = nvram_len as u32 / 4; - let nvram_len_magic = (!nvram_len_words << 16) | nvram_len_words; - self.bus - .bp_write32(ram_addr + CHIP.chip_ram_size - 4, nvram_len_magic) - .await; - - // Start core! - info!("starting up core..."); - self.core_reset(Core::WLAN).await; - assert!(self.core_is_up(Core::WLAN).await); - - while self.bus.read8(FUNC_BACKPLANE, REG_BACKPLANE_CHIP_CLOCK_CSR).await & 0x80 == 0 {} - - // "Set up the interrupt mask and enable interrupts" - self.bus.bp_write32(CHIP.sdiod_core_base_address + 0x24, 0xF0).await; - - // "Lower F2 Watermark to avoid DMA Hang in F2 when SD Clock is stopped." - // Sounds scary... - self.bus - .write8(FUNC_BACKPLANE, REG_BACKPLANE_FUNCTION2_WATERMARK, 32) - .await; - - // wait for wifi startup - info!("waiting for wifi init..."); - while self.bus.read32(FUNC_BUS, REG_BUS_STATUS).await & STATUS_F2_RX_READY == 0 {} - - // Some random configs related to sleep. - // These aren't needed if we don't want to sleep the bus. - // TODO do we need to sleep the bus to read the irq line, due to - // being on the same pin as MOSI/MISO? - - /* - let mut val = self.bus.read8(FUNC_BACKPLANE, REG_BACKPLANE_WAKEUP_CTRL).await; - val |= 0x02; // WAKE_TILL_HT_AVAIL - self.bus.write8(FUNC_BACKPLANE, REG_BACKPLANE_WAKEUP_CTRL, val).await; - self.bus.write8(FUNC_BUS, 0xF0, 0x08).await; // SDIOD_CCCR_BRCM_CARDCAP.CMD_NODEC = 1 - self.bus.write8(FUNC_BACKPLANE, REG_BACKPLANE_CHIP_CLOCK_CSR, 0x02).await; // SBSDIO_FORCE_HT - - let mut val = self.bus.read8(FUNC_BACKPLANE, REG_BACKPLANE_SLEEP_CSR).await; - val |= 0x01; // SBSDIO_SLPCSR_KEEP_SDIO_ON - self.bus.write8(FUNC_BACKPLANE, REG_BACKPLANE_SLEEP_CSR, val).await; - */ - - // clear pulls - self.bus.write8(FUNC_BACKPLANE, REG_BACKPLANE_PULL_UP, 0).await; - let _ = self.bus.read8(FUNC_BACKPLANE, REG_BACKPLANE_PULL_UP).await; - - // start HT clock - //self.bus.write8(FUNC_BACKPLANE, REG_BACKPLANE_CHIP_CLOCK_CSR, 0x10).await; - //info!("waiting for HT clock..."); - //while self.bus.read8(FUNC_BACKPLANE, REG_BACKPLANE_CHIP_CLOCK_CSR).await & 0x80 == 0 {} - //info!("clock ok"); - - #[cfg(feature = "firmware-logs")] - self.log_init().await; - - info!("init done "); - } - - #[cfg(feature = "firmware-logs")] - async fn log_init(&mut self) { - // Initialize shared memory for logging. - - let addr = CHIP.atcm_ram_base_address + CHIP.chip_ram_size - 4 - CHIP.socram_srmem_size; - let shared_addr = self.bus.bp_read32(addr).await; - info!("shared_addr {:08x}", shared_addr); - - let mut shared = [0; SharedMemData::SIZE]; - self.bus.bp_read(shared_addr, &mut shared).await; - let shared = SharedMemData::from_bytes(&shared); - info!("shared: {:08x}", shared); - - self.log.addr = shared.console_addr + 8; - } - - #[cfg(feature = "firmware-logs")] - async fn log_read(&mut self) { - // Read log struct - let mut log = [0; SharedMemLog::SIZE]; - self.bus.bp_read(self.log.addr, &mut log).await; - let log = SharedMemLog::from_bytes(&log); - - let idx = log.idx as usize; - - // If pointer hasn't moved, no need to do anything. - if idx == self.log.last_idx { - return; - } - - // Read entire buf for now. We could read only what we need, but then we - // run into annoying alignment issues in `bp_read`. - let mut buf = [0; 0x400]; - self.bus.bp_read(log.buf, &mut buf).await; - - while self.log.last_idx != idx as usize { - let b = buf[self.log.last_idx]; - if b == b'\r' || b == b'\n' { - if self.log.buf_count != 0 { - let s = unsafe { core::str::from_utf8_unchecked(&self.log.buf[..self.log.buf_count]) }; - debug!("LOGS: {}", s); - self.log.buf_count = 0; - } - } else if self.log.buf_count < self.log.buf.len() { - self.log.buf[self.log.buf_count] = b; - self.log.buf_count += 1; - } - - self.log.last_idx += 1; - if self.log.last_idx == 0x400 { - self.log.last_idx = 0; - } - } - } - - pub async fn run(mut self) -> ! { - let mut buf = [0; 512]; - loop { - #[cfg(feature = "firmware-logs")] - self.log_read().await; - - // Send stuff - // TODO flow control not yet complete - if !self.has_credit() { - warn!("TX stalled"); - } else { - if let IoctlState::Pending { kind, cmd, iface, buf } = self.ioctl_state.get() { - self.send_ioctl(kind, cmd, iface, unsafe { &*buf }).await; - self.ioctl_state.set(IoctlState::Sent { buf }); - } - if !self.has_credit() { - warn!("TX stalled"); - } else { - if let Some(packet) = self.ch.try_tx_buf() { - trace!("tx pkt {:02x}", &packet[..packet.len().min(48)]); - - let mut buf = [0; 512]; - let buf8 = slice8_mut(&mut buf); - - let total_len = SdpcmHeader::SIZE + BcdHeader::SIZE + packet.len(); - - let seq = self.sdpcm_seq; - self.sdpcm_seq = self.sdpcm_seq.wrapping_add(1); - - let sdpcm_header = SdpcmHeader { - len: total_len as u16, // TODO does this len need to be rounded up to u32? - len_inv: !total_len as u16, - sequence: seq, - channel_and_flags: CHANNEL_TYPE_DATA, - next_length: 0, - header_length: SdpcmHeader::SIZE as _, - wireless_flow_control: 0, - bus_data_credit: 0, - reserved: [0, 0], - }; - - let bcd_header = BcdHeader { - flags: BDC_VERSION << BDC_VERSION_SHIFT, - priority: 0, - flags2: 0, - data_offset: 0, - }; - trace!("tx {:?}", sdpcm_header); - trace!(" {:?}", bcd_header); - - buf8[0..SdpcmHeader::SIZE].copy_from_slice(&sdpcm_header.to_bytes()); - buf8[SdpcmHeader::SIZE..][..BcdHeader::SIZE].copy_from_slice(&bcd_header.to_bytes()); - buf8[SdpcmHeader::SIZE + BcdHeader::SIZE..][..packet.len()].copy_from_slice(packet); - - let total_len = (total_len + 3) & !3; // round up to 4byte - - trace!(" {:02x}", &buf8[..total_len.min(48)]); - - self.bus.wlan_write(&buf[..(total_len / 4)]).await; - self.ch.tx_done(); - } - } - } - - // Receive stuff - let irq = self.bus.read16(FUNC_BUS, REG_BUS_INTERRUPT).await; - - if irq & IRQ_F2_PACKET_AVAILABLE != 0 { - let mut status = 0xFFFF_FFFF; - while status == 0xFFFF_FFFF { - status = self.bus.read32(FUNC_BUS, REG_BUS_STATUS).await; - } - - if status & STATUS_F2_PKT_AVAILABLE != 0 { - let len = (status & STATUS_F2_PKT_LEN_MASK) >> STATUS_F2_PKT_LEN_SHIFT; - self.bus.wlan_read(&mut buf, len).await; - trace!("rx {:02x}", &slice8_mut(&mut buf)[..(len as usize).min(48)]); - self.rx(&slice8_mut(&mut buf)[..len as usize]); - } - } - - // TODO use IRQs - yield_now().await; - } - } - - fn rx(&mut self, packet: &[u8]) { - if packet.len() < SdpcmHeader::SIZE { - warn!("packet too short, len={}", packet.len()); - return; - } - - let sdpcm_header = SdpcmHeader::from_bytes(packet[..SdpcmHeader::SIZE].try_into().unwrap()); - trace!("rx {:?}", sdpcm_header); - if sdpcm_header.len != !sdpcm_header.len_inv { - warn!("len inv mismatch"); - return; - } - if sdpcm_header.len as usize != packet.len() { - // TODO: is this guaranteed?? - warn!("len from header doesn't match len from spi"); - return; - } - - self.update_credit(&sdpcm_header); - - let channel = sdpcm_header.channel_and_flags & 0x0f; - - let payload = &packet[sdpcm_header.header_length as _..]; - - match channel { - CHANNEL_TYPE_CONTROL => { - if payload.len() < CdcHeader::SIZE { - warn!("payload too short, len={}", payload.len()); - return; - } - - let cdc_header = CdcHeader::from_bytes(payload[..CdcHeader::SIZE].try_into().unwrap()); - trace!(" {:?}", cdc_header); - - if let IoctlState::Sent { buf } = self.ioctl_state.get() { - if cdc_header.id == self.ioctl_id { - if cdc_header.status != 0 { - // TODO: propagate error instead - panic!("IOCTL error {=i32}", cdc_header.status as i32); - } - - let resp_len = cdc_header.len as usize; - info!("IOCTL Response: {:02x}", &payload[CdcHeader::SIZE..][..resp_len]); - - (unsafe { &mut *buf }[..resp_len]).copy_from_slice(&payload[CdcHeader::SIZE..][..resp_len]); - self.ioctl_state.set(IoctlState::Done { resp_len }); - } - } - } - CHANNEL_TYPE_EVENT => { - let bcd_header = BcdHeader::from_bytes(&payload[..BcdHeader::SIZE].try_into().unwrap()); - trace!(" {:?}", bcd_header); - - let packet_start = BcdHeader::SIZE + 4 * bcd_header.data_offset as usize; - - if packet_start + EventPacket::SIZE > payload.len() { - warn!("BCD event, incomplete header"); - return; - } - let bcd_packet = &payload[packet_start..]; - trace!(" {:02x}", &bcd_packet[..(bcd_packet.len() as usize).min(36)]); - - let mut event_packet = EventPacket::from_bytes(&bcd_packet[..EventPacket::SIZE].try_into().unwrap()); - event_packet.byteswap(); - - const ETH_P_LINK_CTL: u16 = 0x886c; // HPNA, wlan link local tunnel, according to linux if_ether.h - if event_packet.eth.ether_type != ETH_P_LINK_CTL { - warn!( - "unexpected ethernet type 0x{:04x}, expected Broadcom ether type 0x{:04x}", - event_packet.eth.ether_type, ETH_P_LINK_CTL - ); - return; - } - const BROADCOM_OUI: &[u8] = &[0x00, 0x10, 0x18]; - if event_packet.hdr.oui != BROADCOM_OUI { - warn!( - "unexpected ethernet OUI {:02x}, expected Broadcom OUI {:02x}", - event_packet.hdr.oui, BROADCOM_OUI - ); - return; - } - const BCMILCP_SUBTYPE_VENDOR_LONG: u16 = 32769; - if event_packet.hdr.subtype != BCMILCP_SUBTYPE_VENDOR_LONG { - warn!("unexpected subtype {}", event_packet.hdr.subtype); - return; - } - - const BCMILCP_BCM_SUBTYPE_EVENT: u16 = 1; - if event_packet.hdr.user_subtype != BCMILCP_BCM_SUBTYPE_EVENT { - warn!("unexpected user_subtype {}", event_packet.hdr.subtype); - return; - } - - if event_packet.msg.datalen as usize >= (bcd_packet.len() - EventMessage::SIZE) { - warn!("BCD event, incomplete data"); - return; - } - - let evt_type = events::Event::from(event_packet.msg.event_type as u8); - let evt_data = &bcd_packet[EventMessage::SIZE..][..event_packet.msg.datalen as usize]; - debug!("=== EVENT {}: {} {:02x}", evt_type, event_packet.msg, evt_data); - - if evt_type == events::Event::AUTH || evt_type == events::Event::JOIN { - self.events.publish_immediate(EventStatus { - status: event_packet.msg.status, - event_type: evt_type, - }); - } - } - CHANNEL_TYPE_DATA => { - let bcd_header = BcdHeader::from_bytes(&payload[..BcdHeader::SIZE].try_into().unwrap()); - trace!(" {:?}", bcd_header); - - let packet_start = BcdHeader::SIZE + 4 * bcd_header.data_offset as usize; - if packet_start > payload.len() { - warn!("packet start out of range."); - return; - } - let packet = &payload[packet_start..]; - trace!("rx pkt {:02x}", &packet[..(packet.len() as usize).min(48)]); - - match self.ch.try_rx_buf() { - Some(buf) => { - buf[..packet.len()].copy_from_slice(packet); - self.ch.rx_done(packet.len()) - } - None => warn!("failed to push rxd packet to the channel."), - } - } - _ => {} - } - } - - fn update_credit(&mut self, sdpcm_header: &SdpcmHeader) { - if sdpcm_header.channel_and_flags & 0xf < 3 { - let mut sdpcm_seq_max = sdpcm_header.bus_data_credit; - if sdpcm_seq_max.wrapping_sub(self.sdpcm_seq) > 0x40 { - sdpcm_seq_max = self.sdpcm_seq + 2; - } - self.sdpcm_seq_max = sdpcm_seq_max; - } - } - - fn has_credit(&self) -> bool { - self.sdpcm_seq != self.sdpcm_seq_max && self.sdpcm_seq_max.wrapping_sub(self.sdpcm_seq) & 0x80 == 0 - } - - async fn send_ioctl(&mut self, kind: IoctlType, cmd: u32, iface: u32, data: &[u8]) { - let mut buf = [0; 512]; - let buf8 = slice8_mut(&mut buf); - - let total_len = SdpcmHeader::SIZE + CdcHeader::SIZE + data.len(); - - let sdpcm_seq = self.sdpcm_seq; - self.sdpcm_seq = self.sdpcm_seq.wrapping_add(1); - self.ioctl_id = self.ioctl_id.wrapping_add(1); - - let sdpcm_header = SdpcmHeader { - len: total_len as u16, // TODO does this len need to be rounded up to u32? - len_inv: !total_len as u16, - sequence: sdpcm_seq, - channel_and_flags: CHANNEL_TYPE_CONTROL, - next_length: 0, - header_length: SdpcmHeader::SIZE as _, - wireless_flow_control: 0, - bus_data_credit: 0, - reserved: [0, 0], - }; - - let cdc_header = CdcHeader { - cmd: cmd, - len: data.len() as _, - flags: kind as u16 | (iface as u16) << 12, - id: self.ioctl_id, - status: 0, - }; - trace!("tx {:?}", sdpcm_header); - trace!(" {:?}", cdc_header); - - buf8[0..SdpcmHeader::SIZE].copy_from_slice(&sdpcm_header.to_bytes()); - buf8[SdpcmHeader::SIZE..][..CdcHeader::SIZE].copy_from_slice(&cdc_header.to_bytes()); - buf8[SdpcmHeader::SIZE + CdcHeader::SIZE..][..data.len()].copy_from_slice(data); - - let total_len = (total_len + 3) & !3; // round up to 4byte - - trace!(" {:02x}", &buf8[..total_len.min(48)]); - - self.bus.wlan_write(&buf[..total_len / 4]).await; - } - - async fn core_disable(&mut self, core: Core) { - let base = core.base_addr(); - - // Dummy read? - let _ = self.bus.bp_read8(base + AI_RESETCTRL_OFFSET).await; - - // Check it isn't already reset - let r = self.bus.bp_read8(base + AI_RESETCTRL_OFFSET).await; - if r & AI_RESETCTRL_BIT_RESET != 0 { - return; - } - - self.bus.bp_write8(base + AI_IOCTRL_OFFSET, 0).await; - let _ = self.bus.bp_read8(base + AI_IOCTRL_OFFSET).await; - - block_for(Duration::from_millis(1)); - - self.bus - .bp_write8(base + AI_RESETCTRL_OFFSET, AI_RESETCTRL_BIT_RESET) - .await; - let _ = self.bus.bp_read8(base + AI_RESETCTRL_OFFSET).await; - } - - async fn core_reset(&mut self, core: Core) { - self.core_disable(core).await; - - let base = core.base_addr(); - self.bus - .bp_write8(base + AI_IOCTRL_OFFSET, AI_IOCTRL_BIT_FGC | AI_IOCTRL_BIT_CLOCK_EN) - .await; - let _ = self.bus.bp_read8(base + AI_IOCTRL_OFFSET).await; - - self.bus.bp_write8(base + AI_RESETCTRL_OFFSET, 0).await; - - Timer::after(Duration::from_millis(1)).await; - - self.bus - .bp_write8(base + AI_IOCTRL_OFFSET, AI_IOCTRL_BIT_CLOCK_EN) - .await; - let _ = self.bus.bp_read8(base + AI_IOCTRL_OFFSET).await; - - Timer::after(Duration::from_millis(1)).await; - } - - async fn core_is_up(&mut self, core: Core) -> bool { - let base = core.base_addr(); - - let io = self.bus.bp_read8(base + AI_IOCTRL_OFFSET).await; - if io & (AI_IOCTRL_BIT_FGC | AI_IOCTRL_BIT_CLOCK_EN) != AI_IOCTRL_BIT_CLOCK_EN { - debug!("core_is_up: returning false due to bad ioctrl {:02x}", io); - return false; - } - - let r = self.bus.bp_read8(base + AI_RESETCTRL_OFFSET).await; - if r & (AI_RESETCTRL_BIT_RESET) != 0 { - debug!("core_is_up: returning false due to bad resetctrl {:02x}", r); - return false; - } - - true - } -} - -fn slice8_mut(x: &mut [u32]) -> &mut [u8] { - let len = x.len() * 4; - unsafe { slice::from_raw_parts_mut(x.as_mut_ptr() as _, len) } -} - -macro_rules! nvram { - ($($s:literal,)*) => { - concat_bytes!($($s, b"\x00",)* b"\x00\x00") - }; -} - -static NVRAM: &'static [u8] = &*nvram!( - b"NVRAMRev=$Rev$", - b"manfid=0x2d0", - b"prodid=0x0727", - b"vendid=0x14e4", - b"devid=0x43e2", - b"boardtype=0x0887", - b"boardrev=0x1100", - b"boardnum=22", - b"macaddr=00:A0:50:b5:59:5e", - b"sromrev=11", - b"boardflags=0x00404001", - b"boardflags3=0x04000000", - b"xtalfreq=37400", - b"nocrc=1", - b"ag0=255", - b"aa2g=1", - b"ccode=ALL", - b"pa0itssit=0x20", - b"extpagain2g=0", - b"pa2ga0=-168,6649,-778", - b"AvVmid_c0=0x0,0xc8", - b"cckpwroffset0=5", - b"maxp2ga0=84", - b"txpwrbckof=6", - b"cckbw202gpo=0", - b"legofdmbw202gpo=0x66111111", - b"mcsbw202gpo=0x77711111", - b"propbw202gpo=0xdd", - b"ofdmdigfilttype=18", - b"ofdmdigfilttypebe=18", - b"papdmode=1", - b"papdvalidtest=1", - b"pacalidx2g=45", - b"papdepsoffset=-30", - b"papdendidx=58", - b"ltecxmux=0", - b"ltecxpadnum=0x0102", - b"ltecxfnsel=0x44", - b"ltecxgcigpio=0x01", - b"il0macaddr=00:90:4c:c5:12:38", - b"wl0id=0x431b", - b"deadman_to=0xffffffff", - b"muxenab=0x100", - b"spurconfig=0x3", - b"glitch_based_crsmin=1", - b"btc_mode=1", -); diff --git a/src/nvram.rs b/src/nvram.rs new file mode 100644 index 00000000..964a3128 --- /dev/null +++ b/src/nvram.rs @@ -0,0 +1,54 @@ +macro_rules! nvram { + ($($s:literal,)*) => { + concat_bytes!($($s, b"\x00",)* b"\x00\x00") + }; +} + +pub static NVRAM: &'static [u8] = &*nvram!( + b"NVRAMRev=$Rev$", + b"manfid=0x2d0", + b"prodid=0x0727", + b"vendid=0x14e4", + b"devid=0x43e2", + b"boardtype=0x0887", + b"boardrev=0x1100", + b"boardnum=22", + b"macaddr=00:A0:50:b5:59:5e", + b"sromrev=11", + b"boardflags=0x00404001", + b"boardflags3=0x04000000", + b"xtalfreq=37400", + b"nocrc=1", + b"ag0=255", + b"aa2g=1", + b"ccode=ALL", + b"pa0itssit=0x20", + b"extpagain2g=0", + b"pa2ga0=-168,6649,-778", + b"AvVmid_c0=0x0,0xc8", + b"cckpwroffset0=5", + b"maxp2ga0=84", + b"txpwrbckof=6", + b"cckbw202gpo=0", + b"legofdmbw202gpo=0x66111111", + b"mcsbw202gpo=0x77711111", + b"propbw202gpo=0xdd", + b"ofdmdigfilttype=18", + b"ofdmdigfilttypebe=18", + b"papdmode=1", + b"papdvalidtest=1", + b"pacalidx2g=45", + b"papdepsoffset=-30", + b"papdendidx=58", + b"ltecxmux=0", + b"ltecxpadnum=0x0102", + b"ltecxfnsel=0x44", + b"ltecxgcigpio=0x01", + b"il0macaddr=00:90:4c:c5:12:38", + b"wl0id=0x431b", + b"deadman_to=0xffffffff", + b"muxenab=0x100", + b"spurconfig=0x3", + b"glitch_based_crsmin=1", + b"btc_mode=1", +); diff --git a/src/runner.rs b/src/runner.rs new file mode 100644 index 00000000..5d840bc5 --- /dev/null +++ b/src/runner.rs @@ -0,0 +1,564 @@ +use core::cell::Cell; +use core::slice; + +use embassy_futures::yield_now; +use embassy_net_driver_channel as ch; +use embassy_sync::pubsub::PubSubBehavior; +use embassy_time::{block_for, Duration, Timer}; +use embedded_hal_1::digital::OutputPin; + +use crate::bus::Bus; +pub use crate::bus::SpiBusCyw43; +use crate::consts::*; +use crate::events::{EventQueue, EventStatus}; +use crate::nvram::NVRAM; +use crate::structs::*; +use crate::{events, Core, IoctlState, IoctlType, CHIP, MTU}; + +#[cfg(feature = "firmware-logs")] +struct LogState { + addr: u32, + last_idx: usize, + buf: [u8; 256], + buf_count: usize, +} + +impl Default for LogState { + fn default() -> Self { + Self { + addr: Default::default(), + last_idx: Default::default(), + buf: [0; 256], + buf_count: Default::default(), + } + } +} + +pub struct Runner<'a, PWR, SPI> { + ch: ch::Runner<'a, MTU>, + bus: Bus, + + ioctl_state: &'a Cell, + ioctl_id: u16, + sdpcm_seq: u8, + sdpcm_seq_max: u8, + + events: &'a EventQueue, + + #[cfg(feature = "firmware-logs")] + log: LogState, +} + +impl<'a, PWR, SPI> Runner<'a, PWR, SPI> +where + PWR: OutputPin, + SPI: SpiBusCyw43, +{ + pub(crate) fn new( + ch: ch::Runner<'a, MTU>, + bus: Bus, + ioctl_state: &'a Cell, + events: &'a EventQueue, + ) -> Self { + Self { + ch, + bus, + ioctl_state, + ioctl_id: 0, + sdpcm_seq: 0, + sdpcm_seq_max: 1, + events, + #[cfg(feature = "firmware-logs")] + log: LogState::default(), + } + } + + pub(crate) async fn init(&mut self, firmware: &[u8]) { + self.bus.init().await; + + // Init ALP (Active Low Power) clock + self.bus + .write8(FUNC_BACKPLANE, REG_BACKPLANE_CHIP_CLOCK_CSR, BACKPLANE_ALP_AVAIL_REQ) + .await; + info!("waiting for clock..."); + while self.bus.read8(FUNC_BACKPLANE, REG_BACKPLANE_CHIP_CLOCK_CSR).await & BACKPLANE_ALP_AVAIL == 0 {} + info!("clock ok"); + + let chip_id = self.bus.bp_read16(0x1800_0000).await; + info!("chip ID: {}", chip_id); + + // Upload firmware. + self.core_disable(Core::WLAN).await; + self.core_reset(Core::SOCSRAM).await; + self.bus.bp_write32(CHIP.socsram_base_address + 0x10, 3).await; + self.bus.bp_write32(CHIP.socsram_base_address + 0x44, 0).await; + + let ram_addr = CHIP.atcm_ram_base_address; + + info!("loading fw"); + self.bus.bp_write(ram_addr, firmware).await; + + info!("loading nvram"); + // Round up to 4 bytes. + let nvram_len = (NVRAM.len() + 3) / 4 * 4; + self.bus + .bp_write(ram_addr + CHIP.chip_ram_size - 4 - nvram_len as u32, NVRAM) + .await; + + let nvram_len_words = nvram_len as u32 / 4; + let nvram_len_magic = (!nvram_len_words << 16) | nvram_len_words; + self.bus + .bp_write32(ram_addr + CHIP.chip_ram_size - 4, nvram_len_magic) + .await; + + // Start core! + info!("starting up core..."); + self.core_reset(Core::WLAN).await; + assert!(self.core_is_up(Core::WLAN).await); + + while self.bus.read8(FUNC_BACKPLANE, REG_BACKPLANE_CHIP_CLOCK_CSR).await & 0x80 == 0 {} + + // "Set up the interrupt mask and enable interrupts" + self.bus.bp_write32(CHIP.sdiod_core_base_address + 0x24, 0xF0).await; + + // "Lower F2 Watermark to avoid DMA Hang in F2 when SD Clock is stopped." + // Sounds scary... + self.bus + .write8(FUNC_BACKPLANE, REG_BACKPLANE_FUNCTION2_WATERMARK, 32) + .await; + + // wait for wifi startup + info!("waiting for wifi init..."); + while self.bus.read32(FUNC_BUS, REG_BUS_STATUS).await & STATUS_F2_RX_READY == 0 {} + + // Some random configs related to sleep. + // These aren't needed if we don't want to sleep the bus. + // TODO do we need to sleep the bus to read the irq line, due to + // being on the same pin as MOSI/MISO? + + /* + let mut val = self.bus.read8(FUNC_BACKPLANE, REG_BACKPLANE_WAKEUP_CTRL).await; + val |= 0x02; // WAKE_TILL_HT_AVAIL + self.bus.write8(FUNC_BACKPLANE, REG_BACKPLANE_WAKEUP_CTRL, val).await; + self.bus.write8(FUNC_BUS, 0xF0, 0x08).await; // SDIOD_CCCR_BRCM_CARDCAP.CMD_NODEC = 1 + self.bus.write8(FUNC_BACKPLANE, REG_BACKPLANE_CHIP_CLOCK_CSR, 0x02).await; // SBSDIO_FORCE_HT + + let mut val = self.bus.read8(FUNC_BACKPLANE, REG_BACKPLANE_SLEEP_CSR).await; + val |= 0x01; // SBSDIO_SLPCSR_KEEP_SDIO_ON + self.bus.write8(FUNC_BACKPLANE, REG_BACKPLANE_SLEEP_CSR, val).await; + */ + + // clear pulls + self.bus.write8(FUNC_BACKPLANE, REG_BACKPLANE_PULL_UP, 0).await; + let _ = self.bus.read8(FUNC_BACKPLANE, REG_BACKPLANE_PULL_UP).await; + + // start HT clock + //self.bus.write8(FUNC_BACKPLANE, REG_BACKPLANE_CHIP_CLOCK_CSR, 0x10).await; + //info!("waiting for HT clock..."); + //while self.bus.read8(FUNC_BACKPLANE, REG_BACKPLANE_CHIP_CLOCK_CSR).await & 0x80 == 0 {} + //info!("clock ok"); + + #[cfg(feature = "firmware-logs")] + self.log_init().await; + + info!("init done "); + } + + #[cfg(feature = "firmware-logs")] + async fn log_init(&mut self) { + // Initialize shared memory for logging. + + let addr = CHIP.atcm_ram_base_address + CHIP.chip_ram_size - 4 - CHIP.socram_srmem_size; + let shared_addr = self.bus.bp_read32(addr).await; + info!("shared_addr {:08x}", shared_addr); + + let mut shared = [0; SharedMemData::SIZE]; + self.bus.bp_read(shared_addr, &mut shared).await; + let shared = SharedMemData::from_bytes(&shared); + info!("shared: {:08x}", shared); + + self.log.addr = shared.console_addr + 8; + } + + #[cfg(feature = "firmware-logs")] + async fn log_read(&mut self) { + // Read log struct + let mut log = [0; SharedMemLog::SIZE]; + self.bus.bp_read(self.log.addr, &mut log).await; + let log = SharedMemLog::from_bytes(&log); + + let idx = log.idx as usize; + + // If pointer hasn't moved, no need to do anything. + if idx == self.log.last_idx { + return; + } + + // Read entire buf for now. We could read only what we need, but then we + // run into annoying alignment issues in `bp_read`. + let mut buf = [0; 0x400]; + self.bus.bp_read(log.buf, &mut buf).await; + + while self.log.last_idx != idx as usize { + let b = buf[self.log.last_idx]; + if b == b'\r' || b == b'\n' { + if self.log.buf_count != 0 { + let s = unsafe { core::str::from_utf8_unchecked(&self.log.buf[..self.log.buf_count]) }; + debug!("LOGS: {}", s); + self.log.buf_count = 0; + } + } else if self.log.buf_count < self.log.buf.len() { + self.log.buf[self.log.buf_count] = b; + self.log.buf_count += 1; + } + + self.log.last_idx += 1; + if self.log.last_idx == 0x400 { + self.log.last_idx = 0; + } + } + } + + pub async fn run(mut self) -> ! { + let mut buf = [0; 512]; + loop { + #[cfg(feature = "firmware-logs")] + self.log_read().await; + + // Send stuff + // TODO flow control not yet complete + if !self.has_credit() { + warn!("TX stalled"); + } else { + if let IoctlState::Pending { kind, cmd, iface, buf } = self.ioctl_state.get() { + self.send_ioctl(kind, cmd, iface, unsafe { &*buf }).await; + self.ioctl_state.set(IoctlState::Sent { buf }); + } + if !self.has_credit() { + warn!("TX stalled"); + } else { + if let Some(packet) = self.ch.try_tx_buf() { + trace!("tx pkt {:02x}", &packet[..packet.len().min(48)]); + + let mut buf = [0; 512]; + let buf8 = slice8_mut(&mut buf); + + let total_len = SdpcmHeader::SIZE + BcdHeader::SIZE + packet.len(); + + let seq = self.sdpcm_seq; + self.sdpcm_seq = self.sdpcm_seq.wrapping_add(1); + + let sdpcm_header = SdpcmHeader { + len: total_len as u16, // TODO does this len need to be rounded up to u32? + len_inv: !total_len as u16, + sequence: seq, + channel_and_flags: CHANNEL_TYPE_DATA, + next_length: 0, + header_length: SdpcmHeader::SIZE as _, + wireless_flow_control: 0, + bus_data_credit: 0, + reserved: [0, 0], + }; + + let bcd_header = BcdHeader { + flags: BDC_VERSION << BDC_VERSION_SHIFT, + priority: 0, + flags2: 0, + data_offset: 0, + }; + trace!("tx {:?}", sdpcm_header); + trace!(" {:?}", bcd_header); + + buf8[0..SdpcmHeader::SIZE].copy_from_slice(&sdpcm_header.to_bytes()); + buf8[SdpcmHeader::SIZE..][..BcdHeader::SIZE].copy_from_slice(&bcd_header.to_bytes()); + buf8[SdpcmHeader::SIZE + BcdHeader::SIZE..][..packet.len()].copy_from_slice(packet); + + let total_len = (total_len + 3) & !3; // round up to 4byte + + trace!(" {:02x}", &buf8[..total_len.min(48)]); + + self.bus.wlan_write(&buf[..(total_len / 4)]).await; + self.ch.tx_done(); + } + } + } + + // Receive stuff + let irq = self.bus.read16(FUNC_BUS, REG_BUS_INTERRUPT).await; + + if irq & IRQ_F2_PACKET_AVAILABLE != 0 { + let mut status = 0xFFFF_FFFF; + while status == 0xFFFF_FFFF { + status = self.bus.read32(FUNC_BUS, REG_BUS_STATUS).await; + } + + if status & STATUS_F2_PKT_AVAILABLE != 0 { + let len = (status & STATUS_F2_PKT_LEN_MASK) >> STATUS_F2_PKT_LEN_SHIFT; + self.bus.wlan_read(&mut buf, len).await; + trace!("rx {:02x}", &slice8_mut(&mut buf)[..(len as usize).min(48)]); + self.rx(&slice8_mut(&mut buf)[..len as usize]); + } + } + + // TODO use IRQs + yield_now().await; + } + } + + fn rx(&mut self, packet: &[u8]) { + if packet.len() < SdpcmHeader::SIZE { + warn!("packet too short, len={}", packet.len()); + return; + } + + let sdpcm_header = SdpcmHeader::from_bytes(packet[..SdpcmHeader::SIZE].try_into().unwrap()); + trace!("rx {:?}", sdpcm_header); + if sdpcm_header.len != !sdpcm_header.len_inv { + warn!("len inv mismatch"); + return; + } + if sdpcm_header.len as usize != packet.len() { + // TODO: is this guaranteed?? + warn!("len from header doesn't match len from spi"); + return; + } + + self.update_credit(&sdpcm_header); + + let channel = sdpcm_header.channel_and_flags & 0x0f; + + let payload = &packet[sdpcm_header.header_length as _..]; + + match channel { + CHANNEL_TYPE_CONTROL => { + if payload.len() < CdcHeader::SIZE { + warn!("payload too short, len={}", payload.len()); + return; + } + + let cdc_header = CdcHeader::from_bytes(payload[..CdcHeader::SIZE].try_into().unwrap()); + trace!(" {:?}", cdc_header); + + if let IoctlState::Sent { buf } = self.ioctl_state.get() { + if cdc_header.id == self.ioctl_id { + if cdc_header.status != 0 { + // TODO: propagate error instead + panic!("IOCTL error {=i32}", cdc_header.status as i32); + } + + let resp_len = cdc_header.len as usize; + info!("IOCTL Response: {:02x}", &payload[CdcHeader::SIZE..][..resp_len]); + + (unsafe { &mut *buf }[..resp_len]).copy_from_slice(&payload[CdcHeader::SIZE..][..resp_len]); + self.ioctl_state.set(IoctlState::Done { resp_len }); + } + } + } + CHANNEL_TYPE_EVENT => { + let bcd_header = BcdHeader::from_bytes(&payload[..BcdHeader::SIZE].try_into().unwrap()); + trace!(" {:?}", bcd_header); + + let packet_start = BcdHeader::SIZE + 4 * bcd_header.data_offset as usize; + + if packet_start + EventPacket::SIZE > payload.len() { + warn!("BCD event, incomplete header"); + return; + } + let bcd_packet = &payload[packet_start..]; + trace!(" {:02x}", &bcd_packet[..(bcd_packet.len() as usize).min(36)]); + + let mut event_packet = EventPacket::from_bytes(&bcd_packet[..EventPacket::SIZE].try_into().unwrap()); + event_packet.byteswap(); + + const ETH_P_LINK_CTL: u16 = 0x886c; // HPNA, wlan link local tunnel, according to linux if_ether.h + if event_packet.eth.ether_type != ETH_P_LINK_CTL { + warn!( + "unexpected ethernet type 0x{:04x}, expected Broadcom ether type 0x{:04x}", + event_packet.eth.ether_type, ETH_P_LINK_CTL + ); + return; + } + const BROADCOM_OUI: &[u8] = &[0x00, 0x10, 0x18]; + if event_packet.hdr.oui != BROADCOM_OUI { + warn!( + "unexpected ethernet OUI {:02x}, expected Broadcom OUI {:02x}", + event_packet.hdr.oui, BROADCOM_OUI + ); + return; + } + const BCMILCP_SUBTYPE_VENDOR_LONG: u16 = 32769; + if event_packet.hdr.subtype != BCMILCP_SUBTYPE_VENDOR_LONG { + warn!("unexpected subtype {}", event_packet.hdr.subtype); + return; + } + + const BCMILCP_BCM_SUBTYPE_EVENT: u16 = 1; + if event_packet.hdr.user_subtype != BCMILCP_BCM_SUBTYPE_EVENT { + warn!("unexpected user_subtype {}", event_packet.hdr.subtype); + return; + } + + if event_packet.msg.datalen as usize >= (bcd_packet.len() - EventMessage::SIZE) { + warn!("BCD event, incomplete data"); + return; + } + + let evt_type = events::Event::from(event_packet.msg.event_type as u8); + let evt_data = &bcd_packet[EventMessage::SIZE..][..event_packet.msg.datalen as usize]; + debug!("=== EVENT {}: {} {:02x}", evt_type, event_packet.msg, evt_data); + + if evt_type == events::Event::AUTH || evt_type == events::Event::JOIN { + self.events.publish_immediate(EventStatus { + status: event_packet.msg.status, + event_type: evt_type, + }); + } + } + CHANNEL_TYPE_DATA => { + let bcd_header = BcdHeader::from_bytes(&payload[..BcdHeader::SIZE].try_into().unwrap()); + trace!(" {:?}", bcd_header); + + let packet_start = BcdHeader::SIZE + 4 * bcd_header.data_offset as usize; + if packet_start > payload.len() { + warn!("packet start out of range."); + return; + } + let packet = &payload[packet_start..]; + trace!("rx pkt {:02x}", &packet[..(packet.len() as usize).min(48)]); + + match self.ch.try_rx_buf() { + Some(buf) => { + buf[..packet.len()].copy_from_slice(packet); + self.ch.rx_done(packet.len()) + } + None => warn!("failed to push rxd packet to the channel."), + } + } + _ => {} + } + } + + fn update_credit(&mut self, sdpcm_header: &SdpcmHeader) { + if sdpcm_header.channel_and_flags & 0xf < 3 { + let mut sdpcm_seq_max = sdpcm_header.bus_data_credit; + if sdpcm_seq_max.wrapping_sub(self.sdpcm_seq) > 0x40 { + sdpcm_seq_max = self.sdpcm_seq + 2; + } + self.sdpcm_seq_max = sdpcm_seq_max; + } + } + + fn has_credit(&self) -> bool { + self.sdpcm_seq != self.sdpcm_seq_max && self.sdpcm_seq_max.wrapping_sub(self.sdpcm_seq) & 0x80 == 0 + } + + async fn send_ioctl(&mut self, kind: IoctlType, cmd: u32, iface: u32, data: &[u8]) { + let mut buf = [0; 512]; + let buf8 = slice8_mut(&mut buf); + + let total_len = SdpcmHeader::SIZE + CdcHeader::SIZE + data.len(); + + let sdpcm_seq = self.sdpcm_seq; + self.sdpcm_seq = self.sdpcm_seq.wrapping_add(1); + self.ioctl_id = self.ioctl_id.wrapping_add(1); + + let sdpcm_header = SdpcmHeader { + len: total_len as u16, // TODO does this len need to be rounded up to u32? + len_inv: !total_len as u16, + sequence: sdpcm_seq, + channel_and_flags: CHANNEL_TYPE_CONTROL, + next_length: 0, + header_length: SdpcmHeader::SIZE as _, + wireless_flow_control: 0, + bus_data_credit: 0, + reserved: [0, 0], + }; + + let cdc_header = CdcHeader { + cmd: cmd, + len: data.len() as _, + flags: kind as u16 | (iface as u16) << 12, + id: self.ioctl_id, + status: 0, + }; + trace!("tx {:?}", sdpcm_header); + trace!(" {:?}", cdc_header); + + buf8[0..SdpcmHeader::SIZE].copy_from_slice(&sdpcm_header.to_bytes()); + buf8[SdpcmHeader::SIZE..][..CdcHeader::SIZE].copy_from_slice(&cdc_header.to_bytes()); + buf8[SdpcmHeader::SIZE + CdcHeader::SIZE..][..data.len()].copy_from_slice(data); + + let total_len = (total_len + 3) & !3; // round up to 4byte + + trace!(" {:02x}", &buf8[..total_len.min(48)]); + + self.bus.wlan_write(&buf[..total_len / 4]).await; + } + + async fn core_disable(&mut self, core: Core) { + let base = core.base_addr(); + + // Dummy read? + let _ = self.bus.bp_read8(base + AI_RESETCTRL_OFFSET).await; + + // Check it isn't already reset + let r = self.bus.bp_read8(base + AI_RESETCTRL_OFFSET).await; + if r & AI_RESETCTRL_BIT_RESET != 0 { + return; + } + + self.bus.bp_write8(base + AI_IOCTRL_OFFSET, 0).await; + let _ = self.bus.bp_read8(base + AI_IOCTRL_OFFSET).await; + + block_for(Duration::from_millis(1)); + + self.bus + .bp_write8(base + AI_RESETCTRL_OFFSET, AI_RESETCTRL_BIT_RESET) + .await; + let _ = self.bus.bp_read8(base + AI_RESETCTRL_OFFSET).await; + } + + async fn core_reset(&mut self, core: Core) { + self.core_disable(core).await; + + let base = core.base_addr(); + self.bus + .bp_write8(base + AI_IOCTRL_OFFSET, AI_IOCTRL_BIT_FGC | AI_IOCTRL_BIT_CLOCK_EN) + .await; + let _ = self.bus.bp_read8(base + AI_IOCTRL_OFFSET).await; + + self.bus.bp_write8(base + AI_RESETCTRL_OFFSET, 0).await; + + Timer::after(Duration::from_millis(1)).await; + + self.bus + .bp_write8(base + AI_IOCTRL_OFFSET, AI_IOCTRL_BIT_CLOCK_EN) + .await; + let _ = self.bus.bp_read8(base + AI_IOCTRL_OFFSET).await; + + Timer::after(Duration::from_millis(1)).await; + } + + async fn core_is_up(&mut self, core: Core) -> bool { + let base = core.base_addr(); + + let io = self.bus.bp_read8(base + AI_IOCTRL_OFFSET).await; + if io & (AI_IOCTRL_BIT_FGC | AI_IOCTRL_BIT_CLOCK_EN) != AI_IOCTRL_BIT_CLOCK_EN { + debug!("core_is_up: returning false due to bad ioctrl {:02x}", io); + return false; + } + + let r = self.bus.bp_read8(base + AI_RESETCTRL_OFFSET).await; + if r & (AI_RESETCTRL_BIT_RESET) != 0 { + debug!("core_is_up: returning false due to bad resetctrl {:02x}", r); + return false; + } + + true + } +} + +fn slice8_mut(x: &mut [u32]) -> &mut [u8] { + let len = x.len() * 4; + unsafe { slice::from_raw_parts_mut(x.as_mut_ptr() as _, len) } +} From cffc3fc7956570c66bf1bd259a4f68a8ca02fe58 Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Mon, 27 Mar 2023 03:33:06 +0200 Subject: [PATCH 074/178] Fix build with log. --- src/bus.rs | 12 ++++++------ src/control.rs | 7 ++++--- src/events.rs | 2 +- src/fmt.rs | 29 +++++++++++++++++++++++++++++ src/runner.rs | 29 ++++++++++++++++++----------- src/structs.rs | 8 ++++---- 6 files changed, 62 insertions(+), 25 deletions(-) diff --git a/src/bus.rs b/src/bus.rs index f77b890d..7700a832 100644 --- a/src/bus.rs +++ b/src/bus.rs @@ -49,30 +49,30 @@ where while self .read32_swapped(REG_BUS_TEST_RO) - .inspect(|v| defmt::trace!("{:#x}", v)) + .inspect(|v| trace!("{:#x}", v)) .await != FEEDBEAD {} self.write32_swapped(REG_BUS_TEST_RW, TEST_PATTERN).await; let val = self.read32_swapped(REG_BUS_TEST_RW).await; - defmt::trace!("{:#x}", val); + trace!("{:#x}", val); assert_eq!(val, TEST_PATTERN); let val = self.read32_swapped(REG_BUS_CTRL).await; - defmt::trace!("{:#010b}", (val & 0xff)); + trace!("{:#010b}", (val & 0xff)); // 32-bit word length, little endian (which is the default endianess). self.write32_swapped(REG_BUS_CTRL, WORD_LENGTH_32 | HIGH_SPEED).await; let val = self.read8(FUNC_BUS, REG_BUS_CTRL).await; - defmt::trace!("{:#b}", val); + trace!("{:#b}", val); let val = self.read32(FUNC_BUS, REG_BUS_TEST_RO).await; - defmt::trace!("{:#x}", val); + trace!("{:#x}", val); assert_eq!(val, FEEDBEAD); let val = self.read32(FUNC_BUS, REG_BUS_TEST_RW).await; - defmt::trace!("{:#x}", val); + trace!("{:#x}", val); assert_eq!(val, TEST_PATTERN); } diff --git a/src/control.rs b/src/control.rs index 7f1c9fe8..8bfa033b 100644 --- a/src/control.rs +++ b/src/control.rs @@ -9,6 +9,7 @@ use embassy_time::{Duration, Timer}; pub use crate::bus::SpiBusCyw43; use crate::consts::*; use crate::events::{Event, EventQueue}; +use crate::fmt::Bytes; use crate::structs::*; use crate::{countries, IoctlState, IoctlType, PowerManagementMode}; @@ -75,7 +76,7 @@ impl<'a> Control<'a> { // read MAC addr. let mut mac_addr = [0; 6]; assert_eq!(self.get_iovar("cur_etheraddr", &mut mac_addr).await, 6); - info!("mac addr: {:02x}", mac_addr); + info!("mac addr: {:02x}", Bytes(&mac_addr)); let country = countries::WORLD_WIDE_XX; let country_info = CountryInfo { @@ -205,7 +206,7 @@ impl<'a> Control<'a> { let msg = subscriber.next_message_pure().await; if msg.event_type == Event::AUTH && msg.status != 0 { // retry - defmt::warn!("JOIN failed with status={}", msg.status); + warn!("JOIN failed with status={}", msg.status); self.ioctl(IoctlType::Set, 26, 0, &mut i.to_bytes()).await; } else if msg.event_type == Event::JOIN && msg.status == 0 { // successful join @@ -241,7 +242,7 @@ impl<'a> Control<'a> { } async fn set_iovar(&mut self, name: &str, val: &[u8]) { - info!("set {} = {:02x}", name, val); + info!("set {} = {:02x}", name, Bytes(val)); let mut buf = [0; 64]; buf[..name.len()].copy_from_slice(name.as_bytes()); diff --git a/src/events.rs b/src/events.rs index 9e6bb962..b9c8cca6 100644 --- a/src/events.rs +++ b/src/events.rs @@ -6,7 +6,7 @@ use core::num; use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; use embassy_sync::pubsub::{PubSubChannel, Publisher, Subscriber}; -#[derive(Clone, Copy, PartialEq, Eq, num_enum::FromPrimitive)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, num_enum::FromPrimitive)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] #[repr(u8)] pub enum Event { diff --git a/src/fmt.rs b/src/fmt.rs index f8bb0a03..5730447b 100644 --- a/src/fmt.rs +++ b/src/fmt.rs @@ -1,6 +1,8 @@ #![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."); @@ -226,3 +228,30 @@ impl Try for Result { 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/src/runner.rs b/src/runner.rs index 5d840bc5..9945af3f 100644 --- a/src/runner.rs +++ b/src/runner.rs @@ -11,6 +11,7 @@ use crate::bus::Bus; pub use crate::bus::SpiBusCyw43; use crate::consts::*; use crate::events::{EventQueue, EventStatus}; +use crate::fmt::Bytes; use crate::nvram::NVRAM; use crate::structs::*; use crate::{events, Core, IoctlState, IoctlType, CHIP, MTU}; @@ -23,6 +24,7 @@ struct LogState { buf_count: usize, } +#[cfg(feature = "firmware-logs")] impl Default for LogState { fn default() -> Self { Self { @@ -175,7 +177,6 @@ where let mut shared = [0; SharedMemData::SIZE]; self.bus.bp_read(shared_addr, &mut shared).await; let shared = SharedMemData::from_bytes(&shared); - info!("shared: {:08x}", shared); self.log.addr = shared.console_addr + 8; } @@ -238,7 +239,7 @@ where warn!("TX stalled"); } else { if let Some(packet) = self.ch.try_tx_buf() { - trace!("tx pkt {:02x}", &packet[..packet.len().min(48)]); + trace!("tx pkt {:02x}", Bytes(&packet[..packet.len().min(48)])); let mut buf = [0; 512]; let buf8 = slice8_mut(&mut buf); @@ -275,7 +276,7 @@ where let total_len = (total_len + 3) & !3; // round up to 4byte - trace!(" {:02x}", &buf8[..total_len.min(48)]); + trace!(" {:02x}", Bytes(&buf8[..total_len.min(48)])); self.bus.wlan_write(&buf[..(total_len / 4)]).await; self.ch.tx_done(); @@ -295,7 +296,7 @@ where if status & STATUS_F2_PKT_AVAILABLE != 0 { let len = (status & STATUS_F2_PKT_LEN_MASK) >> STATUS_F2_PKT_LEN_SHIFT; self.bus.wlan_read(&mut buf, len).await; - trace!("rx {:02x}", &slice8_mut(&mut buf)[..(len as usize).min(48)]); + trace!("rx {:02x}", Bytes(&slice8_mut(&mut buf)[..(len as usize).min(48)])); self.rx(&slice8_mut(&mut buf)[..len as usize]); } } @@ -343,11 +344,11 @@ where if cdc_header.id == self.ioctl_id { if cdc_header.status != 0 { // TODO: propagate error instead - panic!("IOCTL error {=i32}", cdc_header.status as i32); + panic!("IOCTL error {}", cdc_header.status as i32); } let resp_len = cdc_header.len as usize; - info!("IOCTL Response: {:02x}", &payload[CdcHeader::SIZE..][..resp_len]); + info!("IOCTL Response: {:02x}", Bytes(&payload[CdcHeader::SIZE..][..resp_len])); (unsafe { &mut *buf }[..resp_len]).copy_from_slice(&payload[CdcHeader::SIZE..][..resp_len]); self.ioctl_state.set(IoctlState::Done { resp_len }); @@ -365,7 +366,7 @@ where return; } let bcd_packet = &payload[packet_start..]; - trace!(" {:02x}", &bcd_packet[..(bcd_packet.len() as usize).min(36)]); + trace!(" {:02x}", Bytes(&bcd_packet[..(bcd_packet.len() as usize).min(36)])); let mut event_packet = EventPacket::from_bytes(&bcd_packet[..EventPacket::SIZE].try_into().unwrap()); event_packet.byteswap(); @@ -382,7 +383,8 @@ where if event_packet.hdr.oui != BROADCOM_OUI { warn!( "unexpected ethernet OUI {:02x}, expected Broadcom OUI {:02x}", - event_packet.hdr.oui, BROADCOM_OUI + Bytes(&event_packet.hdr.oui), + Bytes(BROADCOM_OUI) ); return; } @@ -405,7 +407,12 @@ where let evt_type = events::Event::from(event_packet.msg.event_type as u8); let evt_data = &bcd_packet[EventMessage::SIZE..][..event_packet.msg.datalen as usize]; - debug!("=== EVENT {}: {} {:02x}", evt_type, event_packet.msg, evt_data); + debug!( + "=== EVENT {:?}: {:?} {:02x}", + evt_type, + event_packet.msg, + Bytes(evt_data) + ); if evt_type == events::Event::AUTH || evt_type == events::Event::JOIN { self.events.publish_immediate(EventStatus { @@ -424,7 +431,7 @@ where return; } let packet = &payload[packet_start..]; - trace!("rx pkt {:02x}", &packet[..(packet.len() as usize).min(48)]); + trace!("rx pkt {:02x}", Bytes(&packet[..(packet.len() as usize).min(48)])); match self.ch.try_rx_buf() { Some(buf) => { @@ -490,7 +497,7 @@ where let total_len = (total_len + 3) & !3; // round up to 4byte - trace!(" {:02x}", &buf8[..total_len.min(48)]); + trace!(" {:02x}", Bytes(&buf8[..total_len.min(48)])); self.bus.wlan_write(&buf[..total_len / 4]).await; } diff --git a/src/structs.rs b/src/structs.rs index 41a34066..e16808f3 100644 --- a/src/structs.rs +++ b/src/structs.rs @@ -44,7 +44,7 @@ pub struct SharedMemLog { } impl_bytes!(SharedMemLog); -#[derive(Clone, Copy)] +#[derive(Debug, Clone, Copy)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] #[repr(C)] pub struct SdpcmHeader { @@ -67,7 +67,7 @@ pub struct SdpcmHeader { } impl_bytes!(SdpcmHeader); -#[derive(Clone, Copy)] +#[derive(Debug, Clone, Copy)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] #[repr(C)] pub struct CdcHeader { @@ -82,7 +82,7 @@ impl_bytes!(CdcHeader); pub const BDC_VERSION: u8 = 2; pub const BDC_VERSION_SHIFT: u8 = 4; -#[derive(Clone, Copy)] +#[derive(Debug, Clone, Copy)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] #[repr(C)] pub struct BcdHeader { @@ -129,7 +129,7 @@ impl EventHeader { } } -#[derive(Clone, Copy)] +#[derive(Debug, Clone, Copy)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] #[repr(C)] pub struct EventMessage { From ed601d439a222d057f7f19d08ac2cc7d519e831a Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Mon, 27 Mar 2023 03:33:20 +0200 Subject: [PATCH 075/178] Add CI. --- .github/workflows/rust.yml | 29 +++++++++++++++++++++++++++++ Cargo.toml | 6 ++++++ ci.sh | 18 ++++++++++++++++++ 3 files changed, 53 insertions(+) create mode 100644 .github/workflows/rust.yml create mode 100755 ci.sh diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml new file mode 100644 index 00000000..2cd3ba5d --- /dev/null +++ b/.github/workflows/rust.yml @@ -0,0 +1,29 @@ +name: Rust + +on: + push: + branches: [master] + pull_request: + branches: [master] + merge_group: + +env: + CARGO_TERM_COLOR: always + +jobs: + build-nightly: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions/cache@v2 + with: + path: | + ~/.cargo/registry + ~/.cargo/git + target + key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} + + - name: Check fmt + run: cargo fmt -- --check + - name: Build + run: ./ci.sh diff --git a/Cargo.toml b/Cargo.toml index 3bdeb0cf..a307a6cc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,3 +26,9 @@ futures = { version = "0.3.17", default-features = false, features = ["async-awa embedded-hal-1 = { package = "embedded-hal", version = "1.0.0-alpha.9" } num_enum = { version = "0.5.7", default-features = false } + +[patch.crates-io] +embassy-time = { git = "https://github.com/embassy-rs/embassy", rev = "e3f8020c3bdf726dfa451b5b190f27191507a18f" } +embassy-futures = { git = "https://github.com/embassy-rs/embassy", rev = "e3f8020c3bdf726dfa451b5b190f27191507a18f" } +embassy-sync = { git = "https://github.com/embassy-rs/embassy", rev = "e3f8020c3bdf726dfa451b5b190f27191507a18f" } +embassy-net-driver-channel = { git = "https://github.com/embassy-rs/embassy", rev = "e3f8020c3bdf726dfa451b5b190f27191507a18f" } diff --git a/ci.sh b/ci.sh new file mode 100755 index 00000000..1b33564f --- /dev/null +++ b/ci.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +set -euxo pipefail + +# build examples +#================== + +(cd examples/rpi-pico-w; WIFI_NETWORK=foo WIFI_PASSWORD=bar cargo build --release) + + +# build with log/defmt combinations +#===================================== + +cargo build --target thumbv6m-none-eabi --features '' +cargo build --target thumbv6m-none-eabi --features 'log' +cargo build --target thumbv6m-none-eabi --features 'defmt' +cargo build --target thumbv6m-none-eabi --features 'log,firmware-logs' +cargo build --target thumbv6m-none-eabi --features 'defmt,firmware-logs' From 472138122542d6c47e666c8e6bb16cc6635ede0a Mon Sep 17 00:00:00 2001 From: kbleeke Date: Thu, 2 Mar 2023 19:22:43 +0100 Subject: [PATCH 076/178] also wait for join event in join_open --- src/control.rs | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/src/control.rs b/src/control.rs index 8bfa033b..79677b55 100644 --- a/src/control.rs +++ b/src/control.rs @@ -134,7 +134,6 @@ impl<'a> Control<'a> { Timer::after(Duration::from_millis(100)).await; self.state_ch.set_ethernet_address(mac_addr); - self.state_ch.set_link_state(LinkState::Up); // TODO do on join/leave info!("INIT DONE"); } @@ -164,10 +163,8 @@ impl<'a> Control<'a> { ssid: [0; 32], }; i.ssid[..ssid.len()].copy_from_slice(ssid.as_bytes()); - self.ioctl(IoctlType::Set, IOCTL_CMD_SET_SSID, 0, &mut i.to_bytes()) - .await; // set_ssid - info!("JOINED"); + self.wait_for_join(i).await; } pub async fn join_wpa2(&mut self, ssid: &str, passphrase: &str) { @@ -199,21 +196,29 @@ impl<'a> Control<'a> { }; i.ssid[..ssid.len()].copy_from_slice(ssid.as_bytes()); + self.wait_for_join(i).await; + } + + async fn wait_for_join(&mut self, i: SsidInfo) { let mut subscriber = self.event_sub.subscriber().unwrap(); - self.ioctl(IoctlType::Set, 26, 0, &mut i.to_bytes()).await; // set_ssid + self.ioctl(IoctlType::Set, IOCTL_CMD_SET_SSID, 0, &mut i.to_bytes()) + .await; + // set_ssid loop { let msg = subscriber.next_message_pure().await; if msg.event_type == Event::AUTH && msg.status != 0 { // retry warn!("JOIN failed with status={}", msg.status); - self.ioctl(IoctlType::Set, 26, 0, &mut i.to_bytes()).await; + self.ioctl(IoctlType::Set, IOCTL_CMD_SET_SSID, 0, &mut i.to_bytes()) + .await; } else if msg.event_type == Event::JOIN && msg.status == 0 { // successful join break; } } + self.state_ch.set_link_state(LinkState::Up); info!("JOINED"); } From 6f547cf05ddd1a27c8ec4e107ac227f7f9520ba6 Mon Sep 17 00:00:00 2001 From: kbleeke Date: Thu, 2 Mar 2023 15:34:08 +0100 Subject: [PATCH 077/178] asyncify outgoing events --- src/control.rs | 32 +++----------- src/ioctl.rs | 111 ++++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 32 ++------------ src/runner.rs | 118 +++++++++++++++++++++++++++++-------------------- 4 files changed, 190 insertions(+), 103 deletions(-) create mode 100644 src/ioctl.rs diff --git a/src/control.rs b/src/control.rs index 8bfa033b..98f678fb 100644 --- a/src/control.rs +++ b/src/control.rs @@ -1,8 +1,6 @@ -use core::cell::Cell; use core::cmp::{max, min}; use ch::driver::LinkState; -use embassy_futures::yield_now; use embassy_net_driver_channel as ch; use embassy_time::{Duration, Timer}; @@ -10,21 +8,18 @@ pub use crate::bus::SpiBusCyw43; use crate::consts::*; use crate::events::{Event, EventQueue}; use crate::fmt::Bytes; +use crate::ioctl::{IoctlState, IoctlType}; use crate::structs::*; -use crate::{countries, IoctlState, IoctlType, PowerManagementMode}; +use crate::{countries, PowerManagementMode}; pub struct Control<'a> { state_ch: ch::StateRunner<'a>, event_sub: &'a EventQueue, - ioctl_state: &'a Cell, + ioctl_state: &'a IoctlState, } impl<'a> Control<'a> { - pub(crate) fn new( - state_ch: ch::StateRunner<'a>, - event_sub: &'a EventQueue, - ioctl_state: &'a Cell, - ) -> Self { + pub(crate) fn new(state_ch: ch::StateRunner<'a>, event_sub: &'a EventQueue, ioctl_state: &'a IoctlState) -> Self { Self { state_ch, event_sub, @@ -278,23 +273,8 @@ impl<'a> Control<'a> { } async fn ioctl(&mut self, kind: IoctlType, cmd: u32, iface: u32, buf: &mut [u8]) -> usize { - // TODO cancel ioctl on future drop. - - while !matches!(self.ioctl_state.get(), IoctlState::Idle) { - yield_now().await; - } - - self.ioctl_state.set(IoctlState::Pending { kind, cmd, iface, buf }); - - let resp_len = loop { - if let IoctlState::Done { resp_len } = self.ioctl_state.get() { - break resp_len; - } - yield_now().await; - }; - - self.ioctl_state.set(IoctlState::Idle); - + self.ioctl_state.do_ioctl(kind, cmd, iface, buf).await; + let resp_len = self.ioctl_state.wait_complete().await; resp_len } } diff --git a/src/ioctl.rs b/src/ioctl.rs new file mode 100644 index 00000000..2d4cdb87 --- /dev/null +++ b/src/ioctl.rs @@ -0,0 +1,111 @@ +use core::cell::{Cell, RefCell}; +use core::future::poll_fn; +use core::task::{Poll, Waker}; + +use embassy_sync::blocking_mutex::raw::NoopRawMutex; +use embassy_sync::blocking_mutex::Mutex; +use embassy_sync::waitqueue::WakerRegistration; + +#[derive(Clone, Copy)] +pub enum IoctlType { + Get = 0, + Set = 2, +} + +#[derive(Clone, Copy)] +pub struct PendingIoctl { + pub buf: *mut [u8], + pub kind: IoctlType, + pub cmd: u32, + pub iface: u32, +} + +#[derive(Clone, Copy)] +enum IoctlStateInner { + Pending(PendingIoctl), + Sent { buf: *mut [u8] }, + Done { resp_len: usize }, +} + +pub struct IoctlState { + state: Cell, + wakers: Mutex>, +} + +impl IoctlState { + pub fn new() -> Self { + Self { + state: Cell::new(IoctlStateInner::Done { resp_len: 0 }), + wakers: Mutex::new(RefCell::default()), + } + } + + fn wake_control(&self) { + self.wakers.lock(|f| { + f.borrow_mut().0.wake(); + }) + } + + fn register_control(&self, waker: &Waker) { + self.wakers.lock(|f| f.borrow_mut().0.register(waker)); + } + + fn wake_runner(&self) { + self.wakers.lock(|f| { + f.borrow_mut().1.wake(); + }) + } + + fn register_runner(&self, waker: &Waker) { + self.wakers.lock(|f| f.borrow_mut().1.register(waker)); + } + + pub async fn wait_complete(&self) -> usize { + poll_fn(|cx| { + if let IoctlStateInner::Done { resp_len } = self.state.get() { + Poll::Ready(resp_len) + } else { + self.register_control(cx.waker()); + Poll::Pending + } + }) + .await + } + + pub async fn wait_pending(&self) -> PendingIoctl { + let pending = poll_fn(|cx| { + if let IoctlStateInner::Pending(pending) = self.state.get() { + warn!("found pending ioctl"); + Poll::Ready(pending) + } else { + self.register_runner(cx.waker()); + Poll::Pending + } + }) + .await; + + self.state.set(IoctlStateInner::Sent { buf: pending.buf }); + pending + } + + pub async fn do_ioctl(&self, kind: IoctlType, cmd: u32, iface: u32, buf: &mut [u8]) -> usize { + warn!("doing ioctl"); + self.state + .set(IoctlStateInner::Pending(PendingIoctl { buf, kind, cmd, iface })); + self.wake_runner(); + self.wait_complete().await + } + + pub fn ioctl_done(&self, response: &[u8]) { + if let IoctlStateInner::Sent { buf } = self.state.get() { + warn!("ioctl complete"); + // TODO fix this + (unsafe { &mut *buf }[..response.len()]).copy_from_slice(response); + + self.state.set(IoctlStateInner::Done { + resp_len: response.len(), + }); + self.wake_control(); + } + } +} diff --git a/src/lib.rs b/src/lib.rs index af8f74a6..069ca40f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -11,17 +11,17 @@ mod bus; mod consts; mod countries; mod events; +mod ioctl; mod structs; mod control; mod nvram; mod runner; -use core::cell::Cell; - use embassy_net_driver_channel as ch; use embedded_hal_1::digital::OutputPin; use events::EventQueue; +use ioctl::IoctlState; use crate::bus::Bus; pub use crate::bus::SpiBusCyw43; @@ -30,12 +30,6 @@ pub use crate::runner::Runner; const MTU: usize = 1514; -#[derive(Clone, Copy)] -pub enum IoctlType { - Get = 0, - Set = 2, -} - #[allow(unused)] #[derive(Clone, Copy, PartialEq, Eq)] enum Core { @@ -106,26 +100,8 @@ const CHIP: Chip = Chip { chanspec_ctl_sb_mask: 0x0700, }; -#[derive(Clone, Copy)] -enum IoctlState { - Idle, - - Pending { - kind: IoctlType, - cmd: u32, - iface: u32, - buf: *mut [u8], - }, - Sent { - buf: *mut [u8], - }, - Done { - resp_len: usize, - }, -} - pub struct State { - ioctl_state: Cell, + ioctl_state: IoctlState, ch: ch::State, events: EventQueue, } @@ -133,7 +109,7 @@ pub struct State { impl State { pub fn new() -> Self { Self { - ioctl_state: Cell::new(IoctlState::Idle), + ioctl_state: IoctlState::new(), ch: ch::State::new(), events: EventQueue::new(), } diff --git a/src/runner.rs b/src/runner.rs index 9945af3f..4abccf48 100644 --- a/src/runner.rs +++ b/src/runner.rs @@ -1,6 +1,6 @@ -use core::cell::Cell; use core::slice; +use embassy_futures::select::{select3, Either3}; use embassy_futures::yield_now; use embassy_net_driver_channel as ch; use embassy_sync::pubsub::PubSubBehavior; @@ -12,9 +12,10 @@ pub use crate::bus::SpiBusCyw43; use crate::consts::*; use crate::events::{EventQueue, EventStatus}; use crate::fmt::Bytes; +use crate::ioctl::{IoctlState, IoctlType, PendingIoctl}; use crate::nvram::NVRAM; use crate::structs::*; -use crate::{events, Core, IoctlState, IoctlType, CHIP, MTU}; +use crate::{events, Core, CHIP, MTU}; #[cfg(feature = "firmware-logs")] struct LogState { @@ -40,7 +41,7 @@ pub struct Runner<'a, PWR, SPI> { ch: ch::Runner<'a, MTU>, bus: Bus, - ioctl_state: &'a Cell, + ioctl_state: &'a IoctlState, ioctl_id: u16, sdpcm_seq: u8, sdpcm_seq_max: u8, @@ -59,7 +60,7 @@ where pub(crate) fn new( ch: ch::Runner<'a, MTU>, bus: Bus, - ioctl_state: &'a Cell, + ioctl_state: &'a IoctlState, events: &'a EventQueue, ) -> Self { Self { @@ -226,19 +227,22 @@ where #[cfg(feature = "firmware-logs")] self.log_read().await; - // Send stuff - // TODO flow control not yet complete - if !self.has_credit() { - warn!("TX stalled"); - } else { - if let IoctlState::Pending { kind, cmd, iface, buf } = self.ioctl_state.get() { - self.send_ioctl(kind, cmd, iface, unsafe { &*buf }).await; - self.ioctl_state.set(IoctlState::Sent { buf }); - } - if !self.has_credit() { - warn!("TX stalled"); - } else { - if let Some(packet) = self.ch.try_tx_buf() { + let ev = || async { + // TODO use IRQs + yield_now().await; + }; + + if self.has_credit() { + let ioctl = self.ioctl_state.wait_pending(); + let tx = self.ch.tx_buf(); + + match select3(ioctl, tx, ev()).await { + Either3::First(PendingIoctl { buf, kind, cmd, iface }) => { + warn!("ioctl"); + self.send_ioctl(kind, cmd, iface, unsafe { &*buf }).await; + } + Either3::Second(packet) => { + warn!("packet"); trace!("tx pkt {:02x}", Bytes(&packet[..packet.len().min(48)])); let mut buf = [0; 512]; @@ -281,28 +285,46 @@ where self.bus.wlan_write(&buf[..(total_len / 4)]).await; self.ch.tx_done(); } + Either3::Third(()) => { + // Receive stuff + let irq = self.bus.read16(FUNC_BUS, REG_BUS_INTERRUPT).await; + + if irq & IRQ_F2_PACKET_AVAILABLE != 0 { + let mut status = 0xFFFF_FFFF; + while status == 0xFFFF_FFFF { + status = self.bus.read32(FUNC_BUS, REG_BUS_STATUS).await; + } + + if status & STATUS_F2_PKT_AVAILABLE != 0 { + let len = (status & STATUS_F2_PKT_LEN_MASK) >> STATUS_F2_PKT_LEN_SHIFT; + self.bus.wlan_read(&mut buf, len).await; + trace!("rx {:02x}", Bytes(&slice8_mut(&mut buf)[..(len as usize).min(48)])); + self.rx(&slice8_mut(&mut buf)[..len as usize]); + } + } + } + } + } else { + warn!("TX stalled"); + ev().await; + + // Receive stuff + let irq = self.bus.read16(FUNC_BUS, REG_BUS_INTERRUPT).await; + + if irq & IRQ_F2_PACKET_AVAILABLE != 0 { + let mut status = 0xFFFF_FFFF; + while status == 0xFFFF_FFFF { + status = self.bus.read32(FUNC_BUS, REG_BUS_STATUS).await; + } + + if status & STATUS_F2_PKT_AVAILABLE != 0 { + let len = (status & STATUS_F2_PKT_LEN_MASK) >> STATUS_F2_PKT_LEN_SHIFT; + self.bus.wlan_read(&mut buf, len).await; + trace!("rx {:02x}", Bytes(&slice8_mut(&mut buf)[..(len as usize).min(48)])); + self.rx(&slice8_mut(&mut buf)[..len as usize]); + } } } - - // Receive stuff - let irq = self.bus.read16(FUNC_BUS, REG_BUS_INTERRUPT).await; - - if irq & IRQ_F2_PACKET_AVAILABLE != 0 { - let mut status = 0xFFFF_FFFF; - while status == 0xFFFF_FFFF { - status = self.bus.read32(FUNC_BUS, REG_BUS_STATUS).await; - } - - if status & STATUS_F2_PKT_AVAILABLE != 0 { - let len = (status & STATUS_F2_PKT_LEN_MASK) >> STATUS_F2_PKT_LEN_SHIFT; - self.bus.wlan_read(&mut buf, len).await; - trace!("rx {:02x}", Bytes(&slice8_mut(&mut buf)[..(len as usize).min(48)])); - self.rx(&slice8_mut(&mut buf)[..len as usize]); - } - } - - // TODO use IRQs - yield_now().await; } } @@ -340,19 +362,17 @@ where let cdc_header = CdcHeader::from_bytes(payload[..CdcHeader::SIZE].try_into().unwrap()); trace!(" {:?}", cdc_header); - if let IoctlState::Sent { buf } = self.ioctl_state.get() { - if cdc_header.id == self.ioctl_id { - if cdc_header.status != 0 { - // TODO: propagate error instead - panic!("IOCTL error {}", cdc_header.status as i32); - } - - let resp_len = cdc_header.len as usize; - info!("IOCTL Response: {:02x}", Bytes(&payload[CdcHeader::SIZE..][..resp_len])); - - (unsafe { &mut *buf }[..resp_len]).copy_from_slice(&payload[CdcHeader::SIZE..][..resp_len]); - self.ioctl_state.set(IoctlState::Done { resp_len }); + if cdc_header.id == self.ioctl_id { + if cdc_header.status != 0 { + // TODO: propagate error instead + panic!("IOCTL error {}", cdc_header.status as i32); } + + let resp_len = cdc_header.len as usize; + let response = &payload[CdcHeader::SIZE..][..resp_len]; + info!("IOCTL Response: {:02x}", Bytes(response)); + + self.ioctl_state.ioctl_done(response); } } CHANNEL_TYPE_EVENT => { From 4c521044131279aa36f7e21dbc6ec566703a57c6 Mon Sep 17 00:00:00 2001 From: kbleeke Date: Mon, 27 Mar 2023 12:40:27 +0200 Subject: [PATCH 078/178] simplify ioctl waker code --- src/ioctl.rs | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/ioctl.rs b/src/ioctl.rs index 2d4cdb87..6a746559 100644 --- a/src/ioctl.rs +++ b/src/ioctl.rs @@ -2,8 +2,6 @@ use core::cell::{Cell, RefCell}; use core::future::poll_fn; use core::task::{Poll, Waker}; -use embassy_sync::blocking_mutex::raw::NoopRawMutex; -use embassy_sync::blocking_mutex::Mutex; use embassy_sync::waitqueue::WakerRegistration; #[derive(Clone, Copy)] @@ -27,37 +25,39 @@ enum IoctlStateInner { Done { resp_len: usize }, } +#[derive(Default)] +struct Wakers { + control: WakerRegistration, + runner: WakerRegistration, +} + pub struct IoctlState { state: Cell, - wakers: Mutex>, + wakers: RefCell, } impl IoctlState { pub fn new() -> Self { Self { state: Cell::new(IoctlStateInner::Done { resp_len: 0 }), - wakers: Mutex::new(RefCell::default()), + wakers: Default::default(), } } fn wake_control(&self) { - self.wakers.lock(|f| { - f.borrow_mut().0.wake(); - }) + self.wakers.borrow_mut().control.wake(); } fn register_control(&self, waker: &Waker) { - self.wakers.lock(|f| f.borrow_mut().0.register(waker)); + self.wakers.borrow_mut().control.register(waker); } fn wake_runner(&self) { - self.wakers.lock(|f| { - f.borrow_mut().1.wake(); - }) + self.wakers.borrow_mut().runner.wake(); } fn register_runner(&self, waker: &Waker) { - self.wakers.lock(|f| f.borrow_mut().1.register(waker)); + self.wakers.borrow_mut().runner.register(waker); } pub async fn wait_complete(&self) -> usize { From c7646eb699e194a7c692b95b49adc76d6d3295ea Mon Sep 17 00:00:00 2001 From: kbleeke Date: Mon, 27 Mar 2023 12:40:40 +0200 Subject: [PATCH 079/178] bring back TODO note about dropping ioctls --- src/control.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/control.rs b/src/control.rs index 98f678fb..f15a3d3f 100644 --- a/src/control.rs +++ b/src/control.rs @@ -273,6 +273,8 @@ impl<'a> Control<'a> { } async fn ioctl(&mut self, kind: IoctlType, cmd: u32, iface: u32, buf: &mut [u8]) -> usize { + // TODO cancel ioctl on future drop. + self.ioctl_state.do_ioctl(kind, cmd, iface, buf).await; let resp_len = self.ioctl_state.wait_complete().await; resp_len From a2272dda08a2d1625eef0b79fcd80afc8a1e174a Mon Sep 17 00:00:00 2001 From: kbleeke Date: Thu, 2 Mar 2023 19:02:00 +0100 Subject: [PATCH 080/178] status and irq flags formatting with defmt --- src/consts.rs | 147 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 147 insertions(+) diff --git a/src/consts.rs b/src/consts.rs index bee70660..140cb4b6 100644 --- a/src/consts.rs +++ b/src/consts.rs @@ -1,4 +1,5 @@ #![allow(unused)] + pub(crate) const FUNC_BUS: u32 = 0; pub(crate) const FUNC_BACKPLANE: u32 = 1; pub(crate) const FUNC_WLAN: u32 = 2; @@ -103,3 +104,149 @@ pub(crate) const WRITE: bool = true; pub(crate) const READ: bool = false; pub(crate) const INC_ADDR: bool = true; pub(crate) const FIXED_ADDR: bool = false; + +#[allow(dead_code)] +pub(crate) struct FormatStatus(pub u32); + +#[cfg(feature = "defmt")] +impl defmt::Format for FormatStatus { + fn format(&self, fmt: defmt::Formatter) { + macro_rules! implm { + ($($name:ident),*) => { + $( + if self.0 & $name > 0 { + defmt::write!(fmt, " | {}", &stringify!($name)[7..]); + } + )* + }; + } + + implm!( + STATUS_DATA_NOT_AVAILABLE, + STATUS_UNDERFLOW, + STATUS_OVERFLOW, + STATUS_F2_INTR, + STATUS_F3_INTR, + STATUS_F2_RX_READY, + STATUS_F3_RX_READY, + STATUS_HOST_CMD_DATA_ERR, + STATUS_F2_PKT_AVAILABLE, + STATUS_F3_PKT_AVAILABLE + ); + } +} + +#[cfg(feature = "log")] +impl core::fmt::Debug for FormatStatus { + fn fmt(&self, fmt: &mut core::fmt::Formatter) -> core::fmt::Result { + macro_rules! implm { + ($($name:ident),*) => { + $( + if self.0 & $name > 0 { + core::write!(fmt, " | {}", &stringify!($name)[7..])?; + } + )* + }; + } + + implm!( + STATUS_DATA_NOT_AVAILABLE, + STATUS_UNDERFLOW, + STATUS_OVERFLOW, + STATUS_F2_INTR, + STATUS_F3_INTR, + STATUS_F2_RX_READY, + STATUS_F3_RX_READY, + STATUS_HOST_CMD_DATA_ERR, + STATUS_F2_PKT_AVAILABLE, + STATUS_F3_PKT_AVAILABLE + ); + Ok(()) + } +} + +#[cfg(feature = "log")] +impl core::fmt::Display for FormatStatus { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + core::fmt::Debug::fmt(self, f) + } +} + +#[allow(dead_code)] +pub(crate) struct FormatInterrupt(pub u16); + +#[cfg(feature = "defmt")] +impl defmt::Format for FormatInterrupt { + fn format(&self, fmt: defmt::Formatter) { + macro_rules! implm { + ($($name:ident),*) => { + $( + if self.0 & $name > 0 { + defmt::write!(fmt, " | {}", &stringify!($name)[4..]); + } + )* + }; + } + + implm!( + IRQ_DATA_UNAVAILABLE, + IRQ_F2_F3_FIFO_RD_UNDERFLOW, + IRQ_F2_F3_FIFO_WR_OVERFLOW, + IRQ_COMMAND_ERROR, + IRQ_DATA_ERROR, + IRQ_F2_PACKET_AVAILABLE, + IRQ_F3_PACKET_AVAILABLE, + IRQ_F1_OVERFLOW, + IRQ_MISC_INTR0, + IRQ_MISC_INTR1, + IRQ_MISC_INTR2, + IRQ_MISC_INTR3, + IRQ_MISC_INTR4, + IRQ_F1_INTR, + IRQ_F2_INTR, + IRQ_F3_INTR + ); + } +} + +#[cfg(feature = "log")] +impl core::fmt::Debug for FormatInterrupt { + fn fmt(&self, fmt: &mut core::fmt::Formatter) -> core::fmt::Result { + macro_rules! implm { + ($($name:ident),*) => { + $( + if self.0 & $name > 0 { + core::write!(fmt, " | {}", &stringify!($name)[7..])?; + } + )* + }; + } + + implm!( + IRQ_DATA_UNAVAILABLE, + IRQ_F2_F3_FIFO_RD_UNDERFLOW, + IRQ_F2_F3_FIFO_WR_OVERFLOW, + IRQ_COMMAND_ERROR, + IRQ_DATA_ERROR, + IRQ_F2_PACKET_AVAILABLE, + IRQ_F3_PACKET_AVAILABLE, + IRQ_F1_OVERFLOW, + IRQ_MISC_INTR0, + IRQ_MISC_INTR1, + IRQ_MISC_INTR2, + IRQ_MISC_INTR3, + IRQ_MISC_INTR4, + IRQ_F1_INTR, + IRQ_F2_INTR, + IRQ_F3_INTR + ); + Ok(()) + } +} + +#[cfg(feature = "log")] +impl core::fmt::Display for FormatInterrupt { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + core::fmt::Debug::fmt(self, f) + } +} From b58cc2aa239e4adba2c32462cc89133bb7d9f698 Mon Sep 17 00:00:00 2001 From: kbleeke Date: Thu, 2 Mar 2023 19:02:32 +0100 Subject: [PATCH 081/178] use irqs to wait for events --- examples/rpi-pico-w/src/main.rs | 6 ++ examples/rpi-pico-w/src/pio.rs | 17 ++++-- src/bus.rs | 14 ++++- src/consts.rs | 2 + src/ioctl.rs | 3 - src/runner.rs | 98 ++++++++++++++++++--------------- 6 files changed, 87 insertions(+), 53 deletions(-) diff --git a/examples/rpi-pico-w/src/main.rs b/examples/rpi-pico-w/src/main.rs index 43485137..97e2d6a6 100644 --- a/examples/rpi-pico-w/src/main.rs +++ b/examples/rpi-pico-w/src/main.rs @@ -227,4 +227,10 @@ impl cyw43::SpiBusCyw43 for MySpi { self.read(read).await; self.cs.set_high(); } + + async fn wait_for_event(&mut self) {} + + fn clear_event(&mut self) {} + + } diff --git a/examples/rpi-pico-w/src/pio.rs b/examples/rpi-pico-w/src/pio.rs index 8017f4f4..6df22746 100644 --- a/examples/rpi-pico-w/src/pio.rs +++ b/examples/rpi-pico-w/src/pio.rs @@ -41,6 +41,9 @@ where "in pins, 1 side 1" "jmp y-- lp2 side 0" + "wait 1 pin 0 side 0" + "irq 0 side 0" + ".wrap" ); @@ -106,6 +109,7 @@ where } pub async fn write(&mut self, write: &[u32]) { + self.sm.set_enable(false); let write_bits = write.len() * 32 - 1; let read_bits = 31; @@ -124,11 +128,10 @@ where let mut status = 0; self.sm.dma_pull(dma, slice::from_mut(&mut status)).await; defmt::trace!("{:#08x}", status); - - self.sm.set_enable(false); } pub async fn cmd_read(&mut self, cmd: u32, read: &mut [u32]) { + self.sm.set_enable(false); let write_bits = 31; let read_bits = read.len() * 32 - 1; @@ -144,8 +147,6 @@ where self.sm.dma_push(dma.reborrow(), slice::from_ref(&cmd)).await; self.sm.dma_pull(dma, read).await; - - self.sm.set_enable(false); } } @@ -166,4 +167,12 @@ where self.cmd_read(write, read).await; self.cs.set_high(); } + + async fn wait_for_event(&mut self) { + self.sm.wait_irq(0).await; + } + + fn clear_event(&mut self) { + self.sm.clear_irq(0); + } } diff --git a/src/bus.rs b/src/bus.rs index 7700a832..6ec5d0bd 100644 --- a/src/bus.rs +++ b/src/bus.rs @@ -19,6 +19,9 @@ pub trait SpiBusCyw43 { /// Backplane reads have a response delay that produces one extra unspecified word at the beginning of `read`. /// Callers that want to read `n` word from the backplane, have to provide a slice that is `n+1` words long. async fn cmd_read(&mut self, write: u32, read: &mut [u32]); + + async fn wait_for_event(&mut self); + fn clear_event(&mut self); } pub(crate) struct Bus { @@ -63,7 +66,8 @@ where trace!("{:#010b}", (val & 0xff)); // 32-bit word length, little endian (which is the default endianess). - self.write32_swapped(REG_BUS_CTRL, WORD_LENGTH_32 | HIGH_SPEED).await; + self.write32_swapped(REG_BUS_CTRL, WORD_LENGTH_32 | HIGH_SPEED | INTERRUPT_HIGH | WAKE_UP) + .await; let val = self.read8(FUNC_BUS, REG_BUS_CTRL).await; trace!("{:#b}", val); @@ -297,6 +301,14 @@ where self.spi.cmd_write(&buf).await; } + + pub async fn wait_for_event(&mut self) { + self.spi.wait_for_event().await; + } + + pub fn clear_event(&mut self) { + self.spi.clear_event(); + } } fn swap16(x: u32) -> u32 { diff --git a/src/consts.rs b/src/consts.rs index 140cb4b6..70d6660e 100644 --- a/src/consts.rs +++ b/src/consts.rs @@ -14,6 +14,8 @@ pub(crate) const REG_BUS_TEST_RW: u32 = 0x18; pub(crate) const REG_BUS_RESP_DELAY: u32 = 0x1c; pub(crate) const WORD_LENGTH_32: u32 = 0x1; pub(crate) const HIGH_SPEED: u32 = 0x10; +pub(crate) const INTERRUPT_HIGH: u32 = 1 << 5; +pub(crate) const WAKE_UP: u32 = 1 << 7; // SPI_STATUS_REGISTER bits pub(crate) const STATUS_DATA_NOT_AVAILABLE: u32 = 0x00000001; diff --git a/src/ioctl.rs b/src/ioctl.rs index 6a746559..4a2eb252 100644 --- a/src/ioctl.rs +++ b/src/ioctl.rs @@ -75,7 +75,6 @@ impl IoctlState { pub async fn wait_pending(&self) -> PendingIoctl { let pending = poll_fn(|cx| { if let IoctlStateInner::Pending(pending) = self.state.get() { - warn!("found pending ioctl"); Poll::Ready(pending) } else { self.register_runner(cx.waker()); @@ -89,7 +88,6 @@ impl IoctlState { } pub async fn do_ioctl(&self, kind: IoctlType, cmd: u32, iface: u32, buf: &mut [u8]) -> usize { - warn!("doing ioctl"); self.state .set(IoctlStateInner::Pending(PendingIoctl { buf, kind, cmd, iface })); self.wake_runner(); @@ -98,7 +96,6 @@ impl IoctlState { pub fn ioctl_done(&self, response: &[u8]) { if let IoctlStateInner::Sent { buf } = self.state.get() { - warn!("ioctl complete"); // TODO fix this (unsafe { &mut *buf }[..response.len()]).copy_from_slice(response); diff --git a/src/runner.rs b/src/runner.rs index 4abccf48..a1de0770 100644 --- a/src/runner.rs +++ b/src/runner.rs @@ -122,7 +122,11 @@ where while self.bus.read8(FUNC_BACKPLANE, REG_BACKPLANE_CHIP_CLOCK_CSR).await & 0x80 == 0 {} // "Set up the interrupt mask and enable interrupts" - self.bus.bp_write32(CHIP.sdiod_core_base_address + 0x24, 0xF0).await; + // self.bus.bp_write32(CHIP.sdiod_core_base_address + 0x24, 0xF0).await; + + self.bus + .write16(FUNC_BUS, REG_BUS_INTERRUPT_ENABLE, IRQ_F2_PACKET_AVAILABLE) + .await; // "Lower F2 Watermark to avoid DMA Hang in F2 when SD Clock is stopped." // Sounds scary... @@ -227,22 +231,22 @@ where #[cfg(feature = "firmware-logs")] self.log_read().await; - let ev = || async { - // TODO use IRQs - yield_now().await; - }; - if self.has_credit() { let ioctl = self.ioctl_state.wait_pending(); let tx = self.ch.tx_buf(); + let ev = self.bus.wait_for_event(); - match select3(ioctl, tx, ev()).await { - Either3::First(PendingIoctl { buf, kind, cmd, iface }) => { - warn!("ioctl"); - self.send_ioctl(kind, cmd, iface, unsafe { &*buf }).await; + match select3(ioctl, tx, ev).await { + Either3::First(PendingIoctl { + buf: iobuf, + kind, + cmd, + iface, + }) => { + self.send_ioctl(kind, cmd, iface, unsafe { &*iobuf }).await; + self.check_status(&mut buf).await; } Either3::Second(packet) => { - warn!("packet"); trace!("tx pkt {:02x}", Bytes(&packet[..packet.len().min(48)])); let mut buf = [0; 512]; @@ -284,50 +288,54 @@ where self.bus.wlan_write(&buf[..(total_len / 4)]).await; self.ch.tx_done(); + self.check_status(&mut buf).await; } Either3::Third(()) => { - // Receive stuff - let irq = self.bus.read16(FUNC_BUS, REG_BUS_INTERRUPT).await; - - if irq & IRQ_F2_PACKET_AVAILABLE != 0 { - let mut status = 0xFFFF_FFFF; - while status == 0xFFFF_FFFF { - status = self.bus.read32(FUNC_BUS, REG_BUS_STATUS).await; - } - - if status & STATUS_F2_PKT_AVAILABLE != 0 { - let len = (status & STATUS_F2_PKT_LEN_MASK) >> STATUS_F2_PKT_LEN_SHIFT; - self.bus.wlan_read(&mut buf, len).await; - trace!("rx {:02x}", Bytes(&slice8_mut(&mut buf)[..(len as usize).min(48)])); - self.rx(&slice8_mut(&mut buf)[..len as usize]); - } - } + self.handle_irq(&mut buf).await; } } } else { warn!("TX stalled"); - ev().await; - - // Receive stuff - let irq = self.bus.read16(FUNC_BUS, REG_BUS_INTERRUPT).await; - - if irq & IRQ_F2_PACKET_AVAILABLE != 0 { - let mut status = 0xFFFF_FFFF; - while status == 0xFFFF_FFFF { - status = self.bus.read32(FUNC_BUS, REG_BUS_STATUS).await; - } - - if status & STATUS_F2_PKT_AVAILABLE != 0 { - let len = (status & STATUS_F2_PKT_LEN_MASK) >> STATUS_F2_PKT_LEN_SHIFT; - self.bus.wlan_read(&mut buf, len).await; - trace!("rx {:02x}", Bytes(&slice8_mut(&mut buf)[..(len as usize).min(48)])); - self.rx(&slice8_mut(&mut buf)[..len as usize]); - } - } + self.bus.wait_for_event().await; + self.handle_irq(&mut buf).await; } } } + /// Wait for IRQ on F2 packet available + async fn handle_irq(&mut self, buf: &mut [u32; 512]) { + self.bus.clear_event(); + // Receive stuff + let irq = self.bus.read16(FUNC_BUS, REG_BUS_INTERRUPT).await; + trace!("irq{}", FormatInterrupt(irq)); + + if irq & IRQ_F2_PACKET_AVAILABLE != 0 { + self.check_status(buf).await; + } + } + + /// Handle F2 events while status register is set + async fn check_status(&mut self, buf: &mut [u32; 512]) { + loop { + let mut status = 0xFFFF_FFFF; + while status == 0xFFFF_FFFF { + status = self.bus.read32(FUNC_BUS, REG_BUS_STATUS).await; + } + trace!("check status{}", FormatStatus(status)); + + if status & STATUS_F2_PKT_AVAILABLE != 0 { + let len = (status & STATUS_F2_PKT_LEN_MASK) >> STATUS_F2_PKT_LEN_SHIFT; + self.bus.wlan_read(buf, len).await; + trace!("rx {:02x}", Bytes(&slice8_mut(buf)[..(len as usize).min(48)])); + self.rx(&slice8_mut(buf)[..len as usize]); + } else { + break; + } + + yield_now().await; + } + } + fn rx(&mut self, packet: &[u8]) { if packet.len() < SdpcmHeader::SIZE { warn!("packet too short, len={}", packet.len()); From 1c721cb20e44fdc7ec294792a9621d54361d344e Mon Sep 17 00:00:00 2001 From: kbleeke Date: Mon, 27 Mar 2023 13:39:41 +0200 Subject: [PATCH 082/178] cancel ioctl when future is dropped --- src/control.rs | 23 ++++++++++++++++++++--- src/ioctl.rs | 4 ++++ 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/src/control.rs b/src/control.rs index 0dbf6d44..30d5d092 100644 --- a/src/control.rs +++ b/src/control.rs @@ -278,10 +278,27 @@ impl<'a> Control<'a> { } async fn ioctl(&mut self, kind: IoctlType, cmd: u32, iface: u32, buf: &mut [u8]) -> usize { - // TODO cancel ioctl on future drop. + struct CancelOnDrop<'a>(&'a IoctlState); + + impl CancelOnDrop<'_> { + fn defuse(self) { + core::mem::forget(self); + } + } + + impl Drop for CancelOnDrop<'_> { + fn drop(&mut self) { + self.0.cancel_ioctl(); + } + } + + let ioctl = CancelOnDrop(self.ioctl_state); + + ioctl.0.do_ioctl(kind, cmd, iface, buf).await; + let resp_len = ioctl.0.wait_complete().await; + + ioctl.defuse(); - self.ioctl_state.do_ioctl(kind, cmd, iface, buf).await; - let resp_len = self.ioctl_state.wait_complete().await; resp_len } } diff --git a/src/ioctl.rs b/src/ioctl.rs index 6a746559..f5ab410d 100644 --- a/src/ioctl.rs +++ b/src/ioctl.rs @@ -88,6 +88,10 @@ impl IoctlState { pending } + pub fn cancel_ioctl(&self) { + self.state.set(IoctlStateInner::Done { resp_len: 0 }); + } + pub async fn do_ioctl(&self, kind: IoctlType, cmd: u32, iface: u32, buf: &mut [u8]) -> usize { warn!("doing ioctl"); self.state From 8926397f4592f22a5ed54f772a979578ca36628f Mon Sep 17 00:00:00 2001 From: kbleeke Date: Mon, 27 Mar 2023 14:37:39 +0200 Subject: [PATCH 083/178] address irq nits --- examples/rpi-pico-w/src/main.rs | 6 ------ examples/rpi-pico-w/src/pio.rs | 3 --- src/bus.rs | 12 ++++++------ src/runner.rs | 4 ---- 4 files changed, 6 insertions(+), 19 deletions(-) diff --git a/examples/rpi-pico-w/src/main.rs b/examples/rpi-pico-w/src/main.rs index 97e2d6a6..43485137 100644 --- a/examples/rpi-pico-w/src/main.rs +++ b/examples/rpi-pico-w/src/main.rs @@ -227,10 +227,4 @@ impl cyw43::SpiBusCyw43 for MySpi { self.read(read).await; self.cs.set_high(); } - - async fn wait_for_event(&mut self) {} - - fn clear_event(&mut self) {} - - } diff --git a/examples/rpi-pico-w/src/pio.rs b/examples/rpi-pico-w/src/pio.rs index 6df22746..1cefb173 100644 --- a/examples/rpi-pico-w/src/pio.rs +++ b/examples/rpi-pico-w/src/pio.rs @@ -170,9 +170,6 @@ where async fn wait_for_event(&mut self) { self.sm.wait_irq(0).await; - } - - fn clear_event(&mut self) { self.sm.clear_irq(0); } } diff --git a/src/bus.rs b/src/bus.rs index 6ec5d0bd..d2a249f9 100644 --- a/src/bus.rs +++ b/src/bus.rs @@ -1,5 +1,6 @@ use core::slice; +use embassy_futures::yield_now; use embassy_time::{Duration, Timer}; use embedded_hal_1::digital::OutputPin; use futures::FutureExt; @@ -20,8 +21,11 @@ pub trait SpiBusCyw43 { /// Callers that want to read `n` word from the backplane, have to provide a slice that is `n+1` words long. async fn cmd_read(&mut self, write: u32, read: &mut [u32]); - async fn wait_for_event(&mut self); - fn clear_event(&mut self); + /// Wait for events from the Device. A typical implementation would wait for the IRQ pin to be high. + /// The default implementation always reports ready, resulting in active polling of the device. + async fn wait_for_event(&mut self) { + yield_now().await; + } } pub(crate) struct Bus { @@ -305,10 +309,6 @@ where pub async fn wait_for_event(&mut self) { self.spi.wait_for_event().await; } - - pub fn clear_event(&mut self) { - self.spi.clear_event(); - } } fn swap16(x: u32) -> u32 { diff --git a/src/runner.rs b/src/runner.rs index a1de0770..abfac3ae 100644 --- a/src/runner.rs +++ b/src/runner.rs @@ -1,7 +1,6 @@ use core::slice; use embassy_futures::select::{select3, Either3}; -use embassy_futures::yield_now; use embassy_net_driver_channel as ch; use embassy_sync::pubsub::PubSubBehavior; use embassy_time::{block_for, Duration, Timer}; @@ -304,7 +303,6 @@ where /// Wait for IRQ on F2 packet available async fn handle_irq(&mut self, buf: &mut [u32; 512]) { - self.bus.clear_event(); // Receive stuff let irq = self.bus.read16(FUNC_BUS, REG_BUS_INTERRUPT).await; trace!("irq{}", FormatInterrupt(irq)); @@ -331,8 +329,6 @@ where } else { break; } - - yield_now().await; } } From 056df98d475c3be307b7c9c3038e02b4ef79fa08 Mon Sep 17 00:00:00 2001 From: kbleeke Date: Mon, 27 Mar 2023 17:24:45 +0200 Subject: [PATCH 084/178] use send status feature of cyw43 instead of manually checking status --- examples/rpi-pico-w/src/main.rs | 14 ++++++++++++-- examples/rpi-pico-w/src/pio.rs | 24 ++++++++++++++---------- src/bus.rs | 33 +++++++++++++++++++++------------ src/consts.rs | 1 + src/runner.rs | 5 +---- 5 files changed, 49 insertions(+), 28 deletions(-) diff --git a/examples/rpi-pico-w/src/main.rs b/examples/rpi-pico-w/src/main.rs index 43485137..e3c59223 100644 --- a/examples/rpi-pico-w/src/main.rs +++ b/examples/rpi-pico-w/src/main.rs @@ -215,16 +215,26 @@ impl MySpi { } impl cyw43::SpiBusCyw43 for MySpi { - async fn cmd_write(&mut self, write: &[u32]) { + async fn cmd_write(&mut self, write: &[u32]) -> u32 { self.cs.set_low(); self.write(write).await; + + let mut status = 0; + self.read(slice::from_mut(&mut status)).await; + self.cs.set_high(); + status } - async fn cmd_read(&mut self, write: u32, read: &mut [u32]) { + async fn cmd_read(&mut self, write: u32, read: &mut [u32]) -> u32 { self.cs.set_low(); self.write(slice::from_ref(&write)).await; self.read(read).await; + + let mut status = 0; + self.read(slice::from_mut(&mut status)).await; + self.cs.set_high(); + status } } diff --git a/examples/rpi-pico-w/src/pio.rs b/examples/rpi-pico-w/src/pio.rs index 1cefb173..84611306 100644 --- a/examples/rpi-pico-w/src/pio.rs +++ b/examples/rpi-pico-w/src/pio.rs @@ -108,7 +108,7 @@ where } } - pub async fn write(&mut self, write: &[u32]) { + pub async fn write(&mut self, write: &[u32]) -> u32 { self.sm.set_enable(false); let write_bits = write.len() * 32 - 1; let read_bits = 31; @@ -125,15 +125,14 @@ where self.sm.dma_push(dma.reborrow(), write).await; - let mut status = 0; - self.sm.dma_pull(dma, slice::from_mut(&mut status)).await; - defmt::trace!("{:#08x}", status); + let status = self.sm.wait_pull().await; + status } - pub async fn cmd_read(&mut self, cmd: u32, read: &mut [u32]) { + pub async fn cmd_read(&mut self, cmd: u32, read: &mut [u32]) -> u32 { self.sm.set_enable(false); let write_bits = 31; - let read_bits = read.len() * 32 - 1; + let read_bits = read.len() * 32 + 32 - 1; defmt::trace!("write={} read={}", write_bits, read_bits); @@ -147,6 +146,9 @@ where self.sm.dma_push(dma.reborrow(), slice::from_ref(&cmd)).await; self.sm.dma_pull(dma, read).await; + + let status = self.sm.wait_pull().await; + status } } @@ -156,16 +158,18 @@ where SM: PioStateMachine, DMA: Channel, { - async fn cmd_write(&mut self, write: &[u32]) { + async fn cmd_write(&mut self, write: &[u32]) -> u32 { self.cs.set_low(); - self.write(write).await; + let status = self.write(write).await; self.cs.set_high(); + status } - async fn cmd_read(&mut self, write: u32, read: &mut [u32]) { + async fn cmd_read(&mut self, write: u32, read: &mut [u32]) -> u32 { self.cs.set_low(); - self.cmd_read(write, read).await; + let status = self.cmd_read(write, read).await; self.cs.set_high(); + status } async fn wait_for_event(&mut self) { diff --git a/src/bus.rs b/src/bus.rs index d2a249f9..65caea8e 100644 --- a/src/bus.rs +++ b/src/bus.rs @@ -12,14 +12,14 @@ use crate::consts::*; pub trait SpiBusCyw43 { /// Issues a write command on the bus /// First 32 bits of `word` are expected to be a cmd word - async fn cmd_write(&mut self, write: &[u32]); + async fn cmd_write(&mut self, write: &[u32]) -> u32; /// Issues a read command on the bus /// `write` is expected to be a 32 bit cmd word /// `read` will contain the response of the device /// Backplane reads have a response delay that produces one extra unspecified word at the beginning of `read`. /// Callers that want to read `n` word from the backplane, have to provide a slice that is `n+1` words long. - async fn cmd_read(&mut self, write: u32, read: &mut [u32]); + async fn cmd_read(&mut self, write: u32, read: &mut [u32]) -> u32; /// Wait for events from the Device. A typical implementation would wait for the IRQ pin to be high. /// The default implementation always reports ready, resulting in active polling of the device. @@ -32,6 +32,7 @@ pub(crate) struct Bus { backplane_window: u32, pwr: PWR, spi: SPI, + status: u32, } impl Bus @@ -44,6 +45,7 @@ where backplane_window: 0xAAAA_AAAA, pwr, spi, + status: 0, } } @@ -70,8 +72,11 @@ where trace!("{:#010b}", (val & 0xff)); // 32-bit word length, little endian (which is the default endianess). - self.write32_swapped(REG_BUS_CTRL, WORD_LENGTH_32 | HIGH_SPEED | INTERRUPT_HIGH | WAKE_UP) - .await; + self.write32_swapped( + REG_BUS_CTRL, + WORD_LENGTH_32 | HIGH_SPEED | INTERRUPT_HIGH | WAKE_UP | STATUS_ENABLE, + ) + .await; let val = self.read8(FUNC_BUS, REG_BUS_CTRL).await; trace!("{:#b}", val); @@ -88,7 +93,7 @@ where let cmd = cmd_word(READ, INC_ADDR, FUNC_WLAN, 0, len_in_u8); let len_in_u32 = (len_in_u8 as usize + 3) / 4; - self.spi.cmd_read(cmd, &mut buf[..len_in_u32]).await; + self.status = self.spi.cmd_read(cmd, &mut buf[..len_in_u32]).await; } pub async fn wlan_write(&mut self, buf: &[u32]) { @@ -98,7 +103,7 @@ where cmd_buf[0] = cmd; cmd_buf[1..][..buf.len()].copy_from_slice(buf); - self.spi.cmd_write(&cmd_buf).await; + self.status = self.spi.cmd_write(&cmd_buf).await; } #[allow(unused)] @@ -124,7 +129,7 @@ where let cmd = cmd_word(READ, INC_ADDR, FUNC_BACKPLANE, window_offs, len as u32); // round `buf` to word boundary, add one extra word for the response delay - self.spi.cmd_read(cmd, &mut buf[..(len + 3) / 4 + 1]).await; + self.status = self.spi.cmd_read(cmd, &mut buf[..(len + 3) / 4 + 1]).await; // when writing out the data, we skip the response-delay byte data[..len].copy_from_slice(&slice8_mut(&mut buf[1..])[..len]); @@ -157,7 +162,7 @@ where let cmd = cmd_word(WRITE, INC_ADDR, FUNC_BACKPLANE, window_offs, len as u32); buf[0] = cmd; - self.spi.cmd_write(&buf[..(len + 3) / 4 + 1]).await; + self.status = self.spi.cmd_write(&buf[..(len + 3) / 4 + 1]).await; // Advance ptr. addr += len as u32; @@ -273,7 +278,7 @@ where // if we are reading from the backplane, we need an extra word for the response delay let len = if func == FUNC_BACKPLANE { 2 } else { 1 }; - self.spi.cmd_read(cmd, &mut buf[..len]).await; + self.status = self.spi.cmd_read(cmd, &mut buf[..len]).await; // if we read from the backplane, the result is in the second word, after the response delay if func == FUNC_BACKPLANE { @@ -286,7 +291,7 @@ where async fn writen(&mut self, func: u32, addr: u32, val: u32, len: u32) { let cmd = cmd_word(WRITE, INC_ADDR, func, addr, len); - self.spi.cmd_write(&[cmd, val]).await; + self.status = self.spi.cmd_write(&[cmd, val]).await; } async fn read32_swapped(&mut self, addr: u32) -> u32 { @@ -294,7 +299,7 @@ where let cmd = swap16(cmd); let mut buf = [0; 1]; - self.spi.cmd_read(cmd, &mut buf).await; + self.status = self.spi.cmd_read(cmd, &mut buf).await; swap16(buf[0]) } @@ -303,12 +308,16 @@ where let cmd = cmd_word(WRITE, INC_ADDR, FUNC_BUS, addr, 4); let buf = [swap16(cmd), swap16(val)]; - self.spi.cmd_write(&buf).await; + self.status = self.spi.cmd_write(&buf).await; } pub async fn wait_for_event(&mut self) { self.spi.wait_for_event().await; } + + pub fn status(&self) -> u32 { + self.status + } } fn swap16(x: u32) -> u32 { diff --git a/src/consts.rs b/src/consts.rs index 70d6660e..6ed7feb9 100644 --- a/src/consts.rs +++ b/src/consts.rs @@ -16,6 +16,7 @@ pub(crate) const WORD_LENGTH_32: u32 = 0x1; pub(crate) const HIGH_SPEED: u32 = 0x10; pub(crate) const INTERRUPT_HIGH: u32 = 1 << 5; pub(crate) const WAKE_UP: u32 = 1 << 7; +pub(crate) const STATUS_ENABLE: u32 = 0x10000; // SPI_STATUS_REGISTER bits pub(crate) const STATUS_DATA_NOT_AVAILABLE: u32 = 0x00000001; diff --git a/src/runner.rs b/src/runner.rs index abfac3ae..ccdbbf1a 100644 --- a/src/runner.rs +++ b/src/runner.rs @@ -315,10 +315,7 @@ where /// Handle F2 events while status register is set async fn check_status(&mut self, buf: &mut [u32; 512]) { loop { - let mut status = 0xFFFF_FFFF; - while status == 0xFFFF_FFFF { - status = self.bus.read32(FUNC_BUS, REG_BUS_STATUS).await; - } + let status = self.bus.status(); trace!("check status{}", FormatStatus(status)); if status & STATUS_F2_PKT_AVAILABLE != 0 { From 20ea35fc9639487eaa21f1dcee6c32d8a66a0fbb Mon Sep 17 00:00:00 2001 From: kbleeke Date: Mon, 27 Mar 2023 18:04:48 +0200 Subject: [PATCH 085/178] Move pio driver to separate crate --- .vscode/settings.json | 1 + Cargo.toml | 6 ++++++ cyw43-pio/Cargo.toml | 13 +++++++++++++ .../rpi-pico-w/src/pio.rs => cyw43-pio/src/lib.rs | 12 +++++++++--- examples/rpi-pico-w/Cargo.toml | 3 +-- examples/rpi-pico-w/src/main.rs | 5 +---- 6 files changed, 31 insertions(+), 9 deletions(-) create mode 100644 cyw43-pio/Cargo.toml rename examples/rpi-pico-w/src/pio.rs => cyw43-pio/src/lib.rs (93%) diff --git a/.vscode/settings.json b/.vscode/settings.json index dd479929..34430769 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -9,6 +9,7 @@ "rust-analyzer.check.noDefaultFeatures": true, "rust-analyzer.linkedProjects": [ "examples/rpi-pico-w/Cargo.toml", + "cyw43-pio/Cargo.toml", ], "rust-analyzer.server.extraEnv": { "WIFI_NETWORK": "foo", diff --git a/Cargo.toml b/Cargo.toml index a307a6cc..04a47a3a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,3 +32,9 @@ embassy-time = { git = "https://github.com/embassy-rs/embassy", rev = "e3f8020c3 embassy-futures = { git = "https://github.com/embassy-rs/embassy", rev = "e3f8020c3bdf726dfa451b5b190f27191507a18f" } embassy-sync = { git = "https://github.com/embassy-rs/embassy", rev = "e3f8020c3bdf726dfa451b5b190f27191507a18f" } embassy-net-driver-channel = { git = "https://github.com/embassy-rs/embassy", rev = "e3f8020c3bdf726dfa451b5b190f27191507a18f" } +embassy-rp = { git = "https://github.com/embassy-rs/embassy", rev = "e3f8020c3bdf726dfa451b5b190f27191507a18f" } + +[workspace] +members = ["cyw43-pio"] +default-members = ["cyw43-pio", "."] +exclude = ["examples"] \ No newline at end of file diff --git a/cyw43-pio/Cargo.toml b/cyw43-pio/Cargo.toml new file mode 100644 index 00000000..2fc6b759 --- /dev/null +++ b/cyw43-pio/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "cyw43-pio" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +cyw43 = { path = "../" } +embassy-rp = { version = "0.1.0", features = ["defmt", "unstable-traits", "nightly", "unstable-pac", "time-driver", "pio"] } +pio-proc = "0.2" +pio = "0.2.1" +defmt = "0.3" \ No newline at end of file diff --git a/examples/rpi-pico-w/src/pio.rs b/cyw43-pio/src/lib.rs similarity index 93% rename from examples/rpi-pico-w/src/pio.rs rename to cyw43-pio/src/lib.rs index 84611306..2159796c 100644 --- a/examples/rpi-pico-w/src/pio.rs +++ b/cyw43-pio/src/lib.rs @@ -1,3 +1,7 @@ +#![no_std] +#![allow(incomplete_features)] +#![feature(async_fn_in_trait)] + use core::slice; use cyw43::SpiBusCyw43; @@ -125,7 +129,8 @@ where self.sm.dma_push(dma.reborrow(), write).await; - let status = self.sm.wait_pull().await; + let mut status = 0; + self.sm.dma_pull(dma.reborrow(), slice::from_mut(&mut status)).await; status } @@ -145,9 +150,10 @@ where self.sm.set_enable(true); self.sm.dma_push(dma.reborrow(), slice::from_ref(&cmd)).await; - self.sm.dma_pull(dma, read).await; + self.sm.dma_pull(dma.reborrow(), read).await; - let status = self.sm.wait_pull().await; + let mut status = 0; + self.sm.dma_pull(dma.reborrow(), slice::from_mut(&mut status)).await; status } } diff --git a/examples/rpi-pico-w/Cargo.toml b/examples/rpi-pico-w/Cargo.toml index 4a531c88..41ee8a3c 100644 --- a/examples/rpi-pico-w/Cargo.toml +++ b/examples/rpi-pico-w/Cargo.toml @@ -6,6 +6,7 @@ edition = "2021" [dependencies] cyw43 = { path = "../../", features = ["defmt", "firmware-logs"] } +cyw43-pio = { path = "../../cyw43-pio" } embassy-executor = { version = "0.1.0", features = ["defmt", "integrated-timers"] } embassy-time = { version = "0.1.0", features = ["defmt", "defmt-timestamp-uptime"] } embassy-rp = { version = "0.1.0", features = ["defmt", "unstable-traits", "nightly", "unstable-pac", "time-driver", "pio"] } @@ -20,8 +21,6 @@ panic-probe = { version = "0.3", features = ["print-defmt"] } cortex-m = { version = "0.7.6", features = ["critical-section-single-core"] } cortex-m-rt = "0.7.0" futures = { version = "0.3.17", default-features = false, features = ["async-await", "cfg-target-has-atomic", "unstable"] } -pio-proc = "0.2" -pio = "0.2.1" embedded-io = { version = "0.4.0", features = ["async", "defmt"] } heapless = "0.7.15" diff --git a/examples/rpi-pico-w/src/main.rs b/examples/rpi-pico-w/src/main.rs index e3c59223..4b1623be 100644 --- a/examples/rpi-pico-w/src/main.rs +++ b/examples/rpi-pico-w/src/main.rs @@ -4,11 +4,10 @@ #![feature(async_fn_in_trait)] #![allow(incomplete_features)] -mod pio; - use core::slice; use core::str::from_utf8; +use cyw43_pio::PioSpi; use defmt::*; use embassy_executor::Spawner; use embassy_net::tcp::TcpSocket; @@ -20,8 +19,6 @@ use embedded_io::asynch::Write; use static_cell::StaticCell; use {defmt_rtt as _, panic_probe as _}; -use crate::pio::PioSpi; - macro_rules! singleton { ($val:expr) => {{ type T = impl Sized; From 983a94a9c5166f5f4fea103737c6bbe436bec106 Mon Sep 17 00:00:00 2001 From: kbleeke Date: Mon, 27 Mar 2023 22:34:48 +0200 Subject: [PATCH 086/178] update readme --- README.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 5d4347e9..d24ec82f 100644 --- a/README.md +++ b/README.md @@ -10,15 +10,16 @@ Working: - Sending and receiving Ethernet frames. - Using the default MAC address. - [`embassy-net`](https://embassy.dev) integration. +- RP2040 PIO driver for the nonstandard half-duplex SPI used in the Pico W. +- Using IRQ for device events +- GPIO support (for LED on the Pico W) TODO: - AP mode (creating an AP) -- GPIO support (used for the Pico W LED) - Scanning - Setting a custom MAC address. -- RP2040 PIO driver for the nonstandard half-duplex SPI used in the Pico W. Probably porting [this](https://github.com/raspberrypi/pico-sdk/tree/master/src/rp2_common/cyw43_driver). (Currently bitbanging is used). -- Using the IRQ pin instead of polling the bus. +- Investigate why can [this](https://github.com/raspberrypi/pico-sdk/tree/master/src/rp2_common/pico_cyw43_driver) use higher PIO speed. - Bus sleep (unclear what the benefit is. Is it needed for IRQs? or is it just power consumption optimization?) ## Running the example From 781c7f978c555bfe364472fa822f1e27f2da1afb Mon Sep 17 00:00:00 2001 From: kbleeke Date: Tue, 28 Mar 2023 14:03:17 +0200 Subject: [PATCH 087/178] make pio faster --- cyw43-pio/src/lib.rs | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/cyw43-pio/src/lib.rs b/cyw43-pio/src/lib.rs index 2159796c..46ea0411 100644 --- a/cyw43-pio/src/lib.rs +++ b/cyw43-pio/src/lib.rs @@ -33,18 +33,23 @@ where { let program = pio_asm!( ".side_set 1" - // "set pindirs, 1 side 0" - // "set pins, 0 side 0" + ".wrap_target" + // write out x-1 bits "lp:", "out pins, 1 side 0" "jmp x-- lp side 1" + // switch directions "set pindirs, 0 side 0" + // these nops seem to be necessary for fast clkdiv "nop side 1" + "nop side 1" + // read in y-1 bits "lp2:" - "in pins, 1 side 1" - "jmp y-- lp2 side 0" + "in pins, 1 side 0" + "jmp y-- lp2 side 1" + // wait for event and irq host "wait 1 pin 0 side 0" "irq 0 side 0" @@ -64,8 +69,15 @@ where sm.write_instr(relocated.origin() as usize, relocated.code()); + // theoretical maximum according to data sheet, 100Mhz Pio => 50Mhz SPI Freq + // does not work yet, + // sm.set_clkdiv(0x0140); + + // same speed as pico-sdk, 62.5Mhz + sm.set_clkdiv(0x0200); + // 32 Mhz - sm.set_clkdiv(0x03E8); + // sm.set_clkdiv(0x03E8); // 16 Mhz // sm.set_clkdiv(0x07d0); From 869b337715c7c784865e6b1a0c26557c4204d35d Mon Sep 17 00:00:00 2001 From: kbleeke Date: Tue, 28 Mar 2023 16:51:49 +0200 Subject: [PATCH 088/178] PIO at maximum speed --- cyw43-pio/src/lib.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cyw43-pio/src/lib.rs b/cyw43-pio/src/lib.rs index 46ea0411..9c425cba 100644 --- a/cyw43-pio/src/lib.rs +++ b/cyw43-pio/src/lib.rs @@ -43,6 +43,7 @@ where "set pindirs, 0 side 0" // these nops seem to be necessary for fast clkdiv "nop side 1" + "nop side 0" "nop side 1" // read in y-1 bits "lp2:" @@ -70,11 +71,10 @@ where sm.write_instr(relocated.origin() as usize, relocated.code()); // theoretical maximum according to data sheet, 100Mhz Pio => 50Mhz SPI Freq - // does not work yet, - // sm.set_clkdiv(0x0140); + sm.set_clkdiv(0x0140); // same speed as pico-sdk, 62.5Mhz - sm.set_clkdiv(0x0200); + // sm.set_clkdiv(0x0200); // 32 Mhz // sm.set_clkdiv(0x03E8); From b2d63d851df68effce2c4ef7276139380a496c88 Mon Sep 17 00:00:00 2001 From: kbleeke Date: Thu, 30 Mar 2023 12:04:18 +0200 Subject: [PATCH 089/178] set INTERRUPT_WITH_STATUS flag in attempt to prevent hangs --- src/bus.rs | 2 +- src/consts.rs | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/bus.rs b/src/bus.rs index 65caea8e..add346b2 100644 --- a/src/bus.rs +++ b/src/bus.rs @@ -74,7 +74,7 @@ where // 32-bit word length, little endian (which is the default endianess). self.write32_swapped( REG_BUS_CTRL, - WORD_LENGTH_32 | HIGH_SPEED | INTERRUPT_HIGH | WAKE_UP | STATUS_ENABLE, + WORD_LENGTH_32 | HIGH_SPEED | INTERRUPT_HIGH | WAKE_UP | STATUS_ENABLE | INTERRUPT_WITH_STATUS, ) .await; diff --git a/src/consts.rs b/src/consts.rs index 6ed7feb9..fee2d01a 100644 --- a/src/consts.rs +++ b/src/consts.rs @@ -16,7 +16,8 @@ pub(crate) const WORD_LENGTH_32: u32 = 0x1; pub(crate) const HIGH_SPEED: u32 = 0x10; pub(crate) const INTERRUPT_HIGH: u32 = 1 << 5; pub(crate) const WAKE_UP: u32 = 1 << 7; -pub(crate) const STATUS_ENABLE: u32 = 0x10000; +pub(crate) const STATUS_ENABLE: u32 = 1 << 16; +pub(crate) const INTERRUPT_WITH_STATUS: u32 = 1 << 17; // SPI_STATUS_REGISTER bits pub(crate) const STATUS_DATA_NOT_AVAILABLE: u32 = 0x00000001; From 69db1535b21d529abe5c92fa88ea771ca0b1d721 Mon Sep 17 00:00:00 2001 From: kbleeke Date: Thu, 30 Mar 2023 12:24:37 +0200 Subject: [PATCH 090/178] clear DATA_UNAVAILABLE irq --- src/runner.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/runner.rs b/src/runner.rs index ccdbbf1a..deb45f9f 100644 --- a/src/runner.rs +++ b/src/runner.rs @@ -310,6 +310,12 @@ where if irq & IRQ_F2_PACKET_AVAILABLE != 0 { self.check_status(buf).await; } + + if irq & IRQ_DATA_UNAVAILABLE != 0 { + // TODO what should we do here? + warn!("IRQ DATA_UNAVAILABLE, clearing..."); + self.bus.write16(FUNC_BUS, REG_BUS_INTERRUPT, 1).await; + } } /// Handle F2 events while status register is set From 608eb9b1fde5c2d6c5e8f4f5732379de22c2e6c1 Mon Sep 17 00:00:00 2001 From: kbleeke Date: Thu, 30 Mar 2023 17:09:12 +0200 Subject: [PATCH 091/178] event queue mutexs can be noop because we are already !Sync in other places --- src/events.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/events.rs b/src/events.rs index b9c8cca6..87f6c01a 100644 --- a/src/events.rs +++ b/src/events.rs @@ -3,7 +3,7 @@ use core::num; -use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; +use embassy_sync::blocking_mutex::raw::NoopRawMutex; use embassy_sync::pubsub::{PubSubChannel, Publisher, Subscriber}; #[derive(Debug, Clone, Copy, PartialEq, Eq, num_enum::FromPrimitive)] @@ -284,9 +284,9 @@ pub enum Event { LAST = 190, } -pub type EventQueue = PubSubChannel; -pub type EventPublisher<'a> = Publisher<'a, CriticalSectionRawMutex, EventStatus, 2, 1, 1>; -pub type EventSubscriber<'a> = Subscriber<'a, CriticalSectionRawMutex, EventStatus, 2, 1, 1>; +pub type EventQueue = PubSubChannel; +pub type EventPublisher<'a> = Publisher<'a, NoopRawMutex, EventStatus, 2, 1, 1>; +pub type EventSubscriber<'a> = Subscriber<'a, NoopRawMutex, EventStatus, 2, 1, 1>; #[derive(Clone, Copy)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] From 76ebebd0c5b58d230391be4d1989f68f1cc3d5b5 Mon Sep 17 00:00:00 2001 From: kbleeke Date: Fri, 31 Mar 2023 14:18:39 +0200 Subject: [PATCH 092/178] parse data from device in-place --- src/runner.rs | 67 ++++------------------ src/structs.rs | 149 ++++++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 154 insertions(+), 62 deletions(-) diff --git a/src/runner.rs b/src/runner.rs index deb45f9f..f0f6fcee 100644 --- a/src/runner.rs +++ b/src/runner.rs @@ -328,45 +328,23 @@ where let len = (status & STATUS_F2_PKT_LEN_MASK) >> STATUS_F2_PKT_LEN_SHIFT; self.bus.wlan_read(buf, len).await; trace!("rx {:02x}", Bytes(&slice8_mut(buf)[..(len as usize).min(48)])); - self.rx(&slice8_mut(buf)[..len as usize]); + self.rx(&mut slice8_mut(buf)[..len as usize]); } else { break; } } } - fn rx(&mut self, packet: &[u8]) { - if packet.len() < SdpcmHeader::SIZE { - warn!("packet too short, len={}", packet.len()); - return; - } - - let sdpcm_header = SdpcmHeader::from_bytes(packet[..SdpcmHeader::SIZE].try_into().unwrap()); - trace!("rx {:?}", sdpcm_header); - if sdpcm_header.len != !sdpcm_header.len_inv { - warn!("len inv mismatch"); - return; - } - if sdpcm_header.len as usize != packet.len() { - // TODO: is this guaranteed?? - warn!("len from header doesn't match len from spi"); - return; - } + fn rx(&mut self, packet: &mut [u8]) { + let Some((sdpcm_header, payload)) = SdpcmHeader::parse(packet) else { return }; self.update_credit(&sdpcm_header); let channel = sdpcm_header.channel_and_flags & 0x0f; - let payload = &packet[sdpcm_header.header_length as _..]; - match channel { CHANNEL_TYPE_CONTROL => { - if payload.len() < CdcHeader::SIZE { - warn!("payload too short, len={}", payload.len()); - return; - } - - let cdc_header = CdcHeader::from_bytes(payload[..CdcHeader::SIZE].try_into().unwrap()); + let Some((cdc_header, response)) = CdcHeader::parse(payload) else { return; }; trace!(" {:?}", cdc_header); if cdc_header.id == self.ioctl_id { @@ -375,28 +353,21 @@ where panic!("IOCTL error {}", cdc_header.status as i32); } - let resp_len = cdc_header.len as usize; - let response = &payload[CdcHeader::SIZE..][..resp_len]; info!("IOCTL Response: {:02x}", Bytes(response)); self.ioctl_state.ioctl_done(response); } } CHANNEL_TYPE_EVENT => { - let bcd_header = BcdHeader::from_bytes(&payload[..BcdHeader::SIZE].try_into().unwrap()); - trace!(" {:?}", bcd_header); - - let packet_start = BcdHeader::SIZE + 4 * bcd_header.data_offset as usize; - - if packet_start + EventPacket::SIZE > payload.len() { + let Some((_, bcd_packet)) = BcdHeader::parse(payload) else { warn!("BCD event, incomplete header"); return; - } - let bcd_packet = &payload[packet_start..]; - trace!(" {:02x}", Bytes(&bcd_packet[..(bcd_packet.len() as usize).min(36)])); + }; - let mut event_packet = EventPacket::from_bytes(&bcd_packet[..EventPacket::SIZE].try_into().unwrap()); - event_packet.byteswap(); + let Some((event_packet, evt_data)) = EventPacket::parse(bcd_packet) else { + warn!("BCD event, incomplete data"); + return; + }; const ETH_P_LINK_CTL: u16 = 0x886c; // HPNA, wlan link local tunnel, according to linux if_ether.h if event_packet.eth.ether_type != ETH_P_LINK_CTL { @@ -427,13 +398,7 @@ where return; } - if event_packet.msg.datalen as usize >= (bcd_packet.len() - EventMessage::SIZE) { - warn!("BCD event, incomplete data"); - return; - } - let evt_type = events::Event::from(event_packet.msg.event_type as u8); - let evt_data = &bcd_packet[EventMessage::SIZE..][..event_packet.msg.datalen as usize]; debug!( "=== EVENT {:?}: {:?} {:02x}", evt_type, @@ -449,16 +414,8 @@ where } } CHANNEL_TYPE_DATA => { - let bcd_header = BcdHeader::from_bytes(&payload[..BcdHeader::SIZE].try_into().unwrap()); - trace!(" {:?}", bcd_header); - - let packet_start = BcdHeader::SIZE + 4 * bcd_header.data_offset as usize; - if packet_start > payload.len() { - warn!("packet start out of range."); - return; - } - let packet = &payload[packet_start..]; - trace!("rx pkt {:02x}", Bytes(&packet[..(packet.len() as usize).min(48)])); + let Some((_, packet)) = BcdHeader::parse(payload) else { return }; + trace!("rx pkt {:02x}", Bytes(&packet[..packet.len().min(48)])); match self.ch.try_rx_buf() { Some(buf) => { diff --git a/src/structs.rs b/src/structs.rs index e16808f3..6d5d31b0 100644 --- a/src/structs.rs +++ b/src/structs.rs @@ -1,4 +1,5 @@ use crate::events::Event; +use crate::fmt::Bytes; macro_rules! impl_bytes { ($t:ident) => { @@ -11,8 +12,28 @@ macro_rules! impl_bytes { } #[allow(unused)] - pub fn from_bytes(bytes: &[u8; Self::SIZE]) -> Self { - unsafe { core::mem::transmute(*bytes) } + 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) } } } }; @@ -67,9 +88,35 @@ pub struct SdpcmHeader { } impl_bytes!(SdpcmHeader); +impl SdpcmHeader { + pub fn parse(packet: &mut [u8]) -> Option<(&mut Self, &mut [u8])> { + let packet_len = packet.len(); + if packet_len < Self::SIZE { + warn!("packet too short, len={}", packet.len()); + return None; + } + let (sdpcm_header, sdpcm_packet) = packet.split_at_mut(Self::SIZE); + let sdpcm_header = Self::from_bytes_mut(sdpcm_header.try_into().unwrap()); + trace!("rx {:?}", sdpcm_header); + + if sdpcm_header.len != !sdpcm_header.len_inv { + warn!("len inv mismatch"); + return None; + } + + if sdpcm_header.len as usize != packet_len { + warn!("len from header doesn't match len from spi"); + return None; + } + + let sdpcm_packet = &mut sdpcm_packet[(sdpcm_header.header_length as usize - Self::SIZE)..]; + Some((sdpcm_header, sdpcm_packet)) + } +} + #[derive(Debug, Clone, Copy)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -#[repr(C)] +// #[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[repr(C, packed(2))] pub struct CdcHeader { pub cmd: u32, pub len: u32, @@ -79,6 +126,21 @@ pub struct CdcHeader { } impl_bytes!(CdcHeader); +impl CdcHeader { + pub fn parse(packet: &mut [u8]) -> Option<(&mut Self, &mut [u8])> { + if packet.len() < Self::SIZE { + warn!("payload too short, len={}", packet.len()); + return None; + } + + let (cdc_header, payload) = packet.split_at_mut(Self::SIZE); + let cdc_header = Self::from_bytes_mut(cdc_header.try_into().unwrap()); + + let payload = &mut payload[..cdc_header.len as usize]; + Some((cdc_header, payload)) + } +} + pub const BDC_VERSION: u8 = 2; pub const BDC_VERSION_SHIFT: u8 = 4; @@ -95,6 +157,25 @@ pub struct BcdHeader { } impl_bytes!(BcdHeader); +impl BcdHeader { + pub fn parse(packet: &mut [u8]) -> Option<(&mut Self, &mut [u8])> { + if packet.len() < Self::SIZE { + return None; + } + + let (bcd_header, bcd_packet) = packet.split_at_mut(Self::SIZE); + let bcd_header = Self::from_bytes_mut(bcd_header.try_into().unwrap()); + trace!(" {:?}", bcd_header); + + let packet_start = 4 * bcd_header.data_offset as usize; + + let bcd_packet = bcd_packet.get_mut(packet_start..)?; + trace!(" {:02x}", Bytes(&bcd_packet[..bcd_packet.len().min(36)])); + + Some((bcd_header, bcd_packet)) + } +} + #[derive(Clone, Copy)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] #[repr(C)] @@ -130,8 +211,8 @@ impl EventHeader { } #[derive(Debug, Clone, Copy)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -#[repr(C)] +// #[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[repr(C, packed(2))] pub struct EventMessage { /// version pub version: u16, @@ -158,6 +239,45 @@ pub struct EventMessage { } impl_bytes!(EventMessage); +#[cfg(feature = "defmt")] +impl defmt::Format for EventMessage { + fn format(&self, fmt: defmt::Formatter) { + let event_type = self.event_type; + let status = self.status; + let reason = self.reason; + let auth_type = self.auth_type; + let datalen = self.datalen; + + defmt::write!( + fmt, + "EventMessage {{ \ + version: {=u16}, \ + flags: {=u16}, \ + event_type: {=u32}, \ + status: {=u32}, \ + reason: {=u32}, \ + auth_type: {=u32}, \ + datalen: {=u32}, \ + addr: {=[u8; 6]:x}, \ + ifname: {=[u8; 16]:x}, \ + ifidx: {=u8}, \ + bsscfgidx: {=u8}, \ + }} ", + self.version, + self.flags, + event_type, + status, + reason, + auth_type, + datalen, + self.addr, + self.ifname, + self.ifidx, + self.bsscfgidx + ); + } +} + impl EventMessage { pub fn byteswap(&mut self) { self.version = self.version.to_be(); @@ -172,7 +292,7 @@ impl EventMessage { #[derive(Clone, Copy)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] -#[repr(C)] +#[repr(C, packed(2))] pub struct EventPacket { pub eth: EthernetHeader, pub hdr: EventHeader, @@ -181,6 +301,21 @@ pub struct EventPacket { impl_bytes!(EventPacket); impl EventPacket { + pub fn parse(packet: &mut [u8]) -> Option<(&mut Self, &mut [u8])> { + if packet.len() < Self::SIZE { + return None; + } + + let (event_header, event_packet) = packet.split_at_mut(Self::SIZE); + let event_header = Self::from_bytes_mut(event_header.try_into().unwrap()); + // warn!("event_header {:x}", event_header as *const _); + event_header.byteswap(); + + let event_packet = event_packet.get_mut(..event_header.msg.datalen as usize)?; + + Some((event_header, event_packet)) + } + pub fn byteswap(&mut self) { self.eth.byteswap(); self.hdr.byteswap(); From eb32d8ebbd86c0805c9e72f368a718b9977adf12 Mon Sep 17 00:00:00 2001 From: kbleeke Date: Fri, 7 Apr 2023 19:54:05 +0200 Subject: [PATCH 093/178] update embassy --- Cargo.toml | 10 +++++----- examples/rpi-pico-w/Cargo.toml | 18 +++++++++--------- examples/rpi-pico-w/src/main.rs | 2 +- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 04a47a3a..acd3865e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,11 +28,11 @@ embedded-hal-1 = { package = "embedded-hal", version = "1.0.0-alpha.9" } num_enum = { version = "0.5.7", default-features = false } [patch.crates-io] -embassy-time = { git = "https://github.com/embassy-rs/embassy", rev = "e3f8020c3bdf726dfa451b5b190f27191507a18f" } -embassy-futures = { git = "https://github.com/embassy-rs/embassy", rev = "e3f8020c3bdf726dfa451b5b190f27191507a18f" } -embassy-sync = { git = "https://github.com/embassy-rs/embassy", rev = "e3f8020c3bdf726dfa451b5b190f27191507a18f" } -embassy-net-driver-channel = { git = "https://github.com/embassy-rs/embassy", rev = "e3f8020c3bdf726dfa451b5b190f27191507a18f" } -embassy-rp = { git = "https://github.com/embassy-rs/embassy", rev = "e3f8020c3bdf726dfa451b5b190f27191507a18f" } +embassy-time = { git = "https://github.com/embassy-rs/embassy", rev = "047ea9066f0d946fd4d706577b21df38fd3b1647" } +embassy-futures = { git = "https://github.com/embassy-rs/embassy", rev = "047ea9066f0d946fd4d706577b21df38fd3b1647" } +embassy-sync = { git = "https://github.com/embassy-rs/embassy", rev = "047ea9066f0d946fd4d706577b21df38fd3b1647" } +embassy-net-driver-channel = { git = "https://github.com/embassy-rs/embassy", rev = "047ea9066f0d946fd4d706577b21df38fd3b1647" } +embassy-rp = { git = "https://github.com/embassy-rs/embassy", rev = "047ea9066f0d946fd4d706577b21df38fd3b1647" } [workspace] members = ["cyw43-pio"] diff --git a/examples/rpi-pico-w/Cargo.toml b/examples/rpi-pico-w/Cargo.toml index 41ee8a3c..17ee25c3 100644 --- a/examples/rpi-pico-w/Cargo.toml +++ b/examples/rpi-pico-w/Cargo.toml @@ -7,7 +7,7 @@ edition = "2021" [dependencies] cyw43 = { path = "../../", features = ["defmt", "firmware-logs"] } cyw43-pio = { path = "../../cyw43-pio" } -embassy-executor = { version = "0.1.0", features = ["defmt", "integrated-timers"] } +embassy-executor = { version = "0.1.0", features = ["defmt", "integrated-timers", "executor-thread", "arch-cortex-m"] } embassy-time = { version = "0.1.0", features = ["defmt", "defmt-timestamp-uptime"] } embassy-rp = { version = "0.1.0", features = ["defmt", "unstable-traits", "nightly", "unstable-pac", "time-driver", "pio"] } embassy-net = { version = "0.1.0", features = ["defmt", "tcp", "dhcpv4", "medium-ethernet", "unstable-traits", "nightly"] } @@ -27,14 +27,14 @@ heapless = "0.7.15" [patch.crates-io] -embassy-executor = { git = "https://github.com/embassy-rs/embassy", rev = "e3f8020c3bdf726dfa451b5b190f27191507a18f" } -embassy-time = { git = "https://github.com/embassy-rs/embassy", rev = "e3f8020c3bdf726dfa451b5b190f27191507a18f" } -embassy-futures = { git = "https://github.com/embassy-rs/embassy", rev = "e3f8020c3bdf726dfa451b5b190f27191507a18f" } -embassy-sync = { git = "https://github.com/embassy-rs/embassy", rev = "e3f8020c3bdf726dfa451b5b190f27191507a18f" } -embassy-rp = { git = "https://github.com/embassy-rs/embassy", rev = "e3f8020c3bdf726dfa451b5b190f27191507a18f" } -embassy-net = { git = "https://github.com/embassy-rs/embassy", rev = "e3f8020c3bdf726dfa451b5b190f27191507a18f" } -embassy-net-driver = { git = "https://github.com/embassy-rs/embassy", rev = "e3f8020c3bdf726dfa451b5b190f27191507a18f" } -embassy-net-driver-channel = { git = "https://github.com/embassy-rs/embassy", rev = "e3f8020c3bdf726dfa451b5b190f27191507a18f" } +embassy-executor = { git = "https://github.com/embassy-rs/embassy", rev = "047ea9066f0d946fd4d706577b21df38fd3b1647" } +embassy-time = { git = "https://github.com/embassy-rs/embassy", rev = "047ea9066f0d946fd4d706577b21df38fd3b1647" } +embassy-futures = { git = "https://github.com/embassy-rs/embassy", rev = "047ea9066f0d946fd4d706577b21df38fd3b1647" } +embassy-sync = { git = "https://github.com/embassy-rs/embassy", rev = "047ea9066f0d946fd4d706577b21df38fd3b1647" } +embassy-rp = { git = "https://github.com/embassy-rs/embassy", rev = "047ea9066f0d946fd4d706577b21df38fd3b1647" } +embassy-net = { git = "https://github.com/embassy-rs/embassy", rev = "047ea9066f0d946fd4d706577b21df38fd3b1647" } +embassy-net-driver = { git = "https://github.com/embassy-rs/embassy", rev = "047ea9066f0d946fd4d706577b21df38fd3b1647" } +embassy-net-driver-channel = { git = "https://github.com/embassy-rs/embassy", rev = "047ea9066f0d946fd4d706577b21df38fd3b1647" } [profile.dev] debug = 2 diff --git a/examples/rpi-pico-w/src/main.rs b/examples/rpi-pico-w/src/main.rs index 4b1623be..d5610d2b 100644 --- a/examples/rpi-pico-w/src/main.rs +++ b/examples/rpi-pico-w/src/main.rs @@ -14,7 +14,7 @@ use embassy_net::tcp::TcpSocket; use embassy_net::{Config, Stack, StackResources}; use embassy_rp::gpio::{Flex, Level, Output}; use embassy_rp::peripherals::{DMA_CH0, PIN_23, PIN_24, PIN_25, PIN_29}; -use embassy_rp::pio::{Pio0, PioPeripherial, PioStateMachineInstance, Sm0}; +use embassy_rp::pio::{Pio0, PioPeripheral, PioStateMachineInstance, Sm0}; use embedded_io::asynch::Write; use static_cell::StaticCell; use {defmt_rtt as _, panic_probe as _}; From 056b8ab5a2e277af7ceb845e87b38747ae9d950c Mon Sep 17 00:00:00 2001 From: kbleeke Date: Fri, 7 Apr 2023 19:54:39 +0200 Subject: [PATCH 094/178] update nightly to embassy nightly --- rust-toolchain.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 20c10c3f..88519979 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,7 +1,7 @@ # Before upgrading check that everything is available on all tier1 targets here: # https://rust-lang.github.io/rustup-components-history [toolchain] -channel = "nightly-2023-03-19" +channel = "nightly-2023-04-02" components = [ "rust-src", "rustfmt" ] targets = [ "thumbv6m-none-eabi", From 4d2710ed4d978b71602b9f1c809d721650e0c1e9 Mon Sep 17 00:00:00 2001 From: kbleeke Date: Fri, 7 Apr 2023 19:55:46 +0200 Subject: [PATCH 095/178] pin defmt to 0.3.2. 0.3.4 introduces an undesired wire format upgrade --- examples/rpi-pico-w/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/rpi-pico-w/Cargo.toml b/examples/rpi-pico-w/Cargo.toml index 17ee25c3..dca796e3 100644 --- a/examples/rpi-pico-w/Cargo.toml +++ b/examples/rpi-pico-w/Cargo.toml @@ -14,7 +14,7 @@ embassy-net = { version = "0.1.0", features = ["defmt", "tcp", "dhcpv4", "medium atomic-polyfill = "0.1.5" static_cell = "1.0" -defmt = "0.3" +defmt = "=0.3.2" defmt-rtt = "0.3" panic-probe = { version = "0.3", features = ["print-defmt"] } From 683ad8047996709fce819c90f6cad47c096a57b7 Mon Sep 17 00:00:00 2001 From: kbleeke Date: Fri, 7 Apr 2023 19:56:05 +0200 Subject: [PATCH 096/178] update embedded-hal --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index acd3865e..4ea5d8b6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,7 +24,7 @@ cortex-m = "0.7.3" cortex-m-rt = "0.7.0" futures = { version = "0.3.17", default-features = false, features = ["async-await", "cfg-target-has-atomic", "unstable"] } -embedded-hal-1 = { package = "embedded-hal", version = "1.0.0-alpha.9" } +embedded-hal-1 = { package = "embedded-hal", version = "1.0.0-alpha.10" } num_enum = { version = "0.5.7", default-features = false } [patch.crates-io] From 4be1e4bd44600737bf80ef7210c2b913d63364de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mattias=20Gr=C3=B6nlund?= Date: Fri, 14 Apr 2023 09:38:35 +0200 Subject: [PATCH 097/178] Remove MySpi MySpi was replaced by PioSpi and no longer used. --- examples/rpi-pico-w/src/main.rs | 90 +-------------------------------- 1 file changed, 2 insertions(+), 88 deletions(-) diff --git a/examples/rpi-pico-w/src/main.rs b/examples/rpi-pico-w/src/main.rs index d5610d2b..b773cea1 100644 --- a/examples/rpi-pico-w/src/main.rs +++ b/examples/rpi-pico-w/src/main.rs @@ -4,7 +4,6 @@ #![feature(async_fn_in_trait)] #![allow(incomplete_features)] -use core::slice; use core::str::from_utf8; use cyw43_pio::PioSpi; @@ -12,8 +11,8 @@ use defmt::*; use embassy_executor::Spawner; use embassy_net::tcp::TcpSocket; use embassy_net::{Config, Stack, StackResources}; -use embassy_rp::gpio::{Flex, Level, Output}; -use embassy_rp::peripherals::{DMA_CH0, PIN_23, PIN_24, PIN_25, PIN_29}; +use embassy_rp::gpio::{Level, Output}; +use embassy_rp::peripherals::{DMA_CH0, PIN_23, PIN_25}; use embassy_rp::pio::{Pio0, PioPeripheral, PioStateMachineInstance, Sm0}; use embedded_io::asynch::Write; use static_cell::StaticCell; @@ -150,88 +149,3 @@ async fn main(spawner: Spawner) { } } -struct MySpi { - /// SPI clock - clk: Output<'static, PIN_29>, - - /// 4 signals, all in one!! - /// - SPI MISO - /// - SPI MOSI - /// - IRQ - /// - strap to set to gSPI mode on boot. - dio: Flex<'static, PIN_24>, - - /// Chip select - cs: Output<'static, PIN_25>, -} - -impl MySpi { - async fn read(&mut self, words: &mut [u32]) { - self.dio.set_as_input(); - for word in words { - let mut w = 0; - for _ in 0..32 { - w = w << 1; - - // rising edge, sample data - if self.dio.is_high() { - w |= 0x01; - } - self.clk.set_high(); - - // falling edge - self.clk.set_low(); - } - *word = w - } - } - - async fn write(&mut self, words: &[u32]) { - self.dio.set_as_output(); - for word in words { - let mut word = *word; - for _ in 0..32 { - // falling edge, setup data - self.clk.set_low(); - if word & 0x8000_0000 == 0 { - self.dio.set_low(); - } else { - self.dio.set_high(); - } - - // rising edge - self.clk.set_high(); - - word = word << 1; - } - } - self.clk.set_low(); - - self.dio.set_as_input(); - } -} - -impl cyw43::SpiBusCyw43 for MySpi { - async fn cmd_write(&mut self, write: &[u32]) -> u32 { - self.cs.set_low(); - self.write(write).await; - - let mut status = 0; - self.read(slice::from_mut(&mut status)).await; - - self.cs.set_high(); - status - } - - async fn cmd_read(&mut self, write: u32, read: &mut [u32]) -> u32 { - self.cs.set_low(); - self.write(slice::from_ref(&write)).await; - self.read(read).await; - - let mut status = 0; - self.read(slice::from_mut(&mut status)).await; - - self.cs.set_high(); - status - } -} From 9ca5bcd57686bc8144c31b1937a15dccb6cf07ce Mon Sep 17 00:00:00 2001 From: mattiasgronlund Date: Fri, 14 Apr 2023 10:27:25 +0200 Subject: [PATCH 098/178] Update main.rs --- examples/rpi-pico-w/src/main.rs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/examples/rpi-pico-w/src/main.rs b/examples/rpi-pico-w/src/main.rs index b773cea1..d075aec2 100644 --- a/examples/rpi-pico-w/src/main.rs +++ b/examples/rpi-pico-w/src/main.rs @@ -61,11 +61,6 @@ async fn main(spawner: Spawner) { let pwr = Output::new(p.PIN_23, Level::Low); let cs = Output::new(p.PIN_25, Level::High); - // let clk = Output::new(p.PIN_29, Level::Low); - // let mut dio = Flex::new(p.PIN_24); - // dio.set_low(); - // dio.set_as_output(); - // // let bus = MySpi { clk, dio }; let (_, sm, _, _, _) = p.PIO0.split(); let dma = p.DMA_CH0; From 6a1a3e6877053b1b72adb3c1446f4f077ad3b03e Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Fri, 21 Apr 2023 14:37:04 +0200 Subject: [PATCH 099/178] Workaround regex breaking change. --- examples/rpi-pico-w/Cargo.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/examples/rpi-pico-w/Cargo.toml b/examples/rpi-pico-w/Cargo.toml index dca796e3..d211f926 100644 --- a/examples/rpi-pico-w/Cargo.toml +++ b/examples/rpi-pico-w/Cargo.toml @@ -25,6 +25,9 @@ futures = { version = "0.3.17", default-features = false, features = ["async-awa embedded-io = { version = "0.4.0", features = ["async", "defmt"] } heapless = "0.7.15" +[build-dependencies] +# Workaround https://github.com/embassy-rs/cyw43/issues/68 +regex = { version = "~1.7.3", default-features = false } [patch.crates-io] embassy-executor = { git = "https://github.com/embassy-rs/embassy", rev = "047ea9066f0d946fd4d706577b21df38fd3b1647" } From 2d7ba44621fa35abad07d2ddb8b253e815ce2c1f Mon Sep 17 00:00:00 2001 From: kbleeke Date: Sun, 2 Apr 2023 20:19:47 +0200 Subject: [PATCH 100/178] rework event handling to allow sending data --- src/bus.rs | 8 +--- src/consts.rs | 44 ++++++++++++++++++++ src/control.rs | 19 +++++---- src/events.rs | 111 ++++++++++++++++++++++++++++++++++++++++++++++--- src/ioctl.rs | 6 +++ src/lib.rs | 13 ++++-- src/runner.rs | 33 +++++++-------- 7 files changed, 192 insertions(+), 42 deletions(-) diff --git a/src/bus.rs b/src/bus.rs index add346b2..e26f1112 100644 --- a/src/bus.rs +++ b/src/bus.rs @@ -1,11 +1,10 @@ -use core::slice; - use embassy_futures::yield_now; use embassy_time::{Duration, Timer}; use embedded_hal_1::digital::OutputPin; use futures::FutureExt; use crate::consts::*; +use crate::slice8_mut; /// Custom Spi Trait that _only_ supports the bus operation of the cyw43 /// Implementors are expected to hold the CS pin low during an operation. @@ -327,8 +326,3 @@ fn swap16(x: u32) -> u32 { fn cmd_word(write: bool, incr: bool, func: u32, addr: u32, len: u32) -> u32 { (write as u32) << 31 | (incr as u32) << 30 | (func & 0b11) << 28 | (addr & 0x1FFFF) << 11 | (len & 0x7FF) } - -fn slice8_mut(x: &mut [u32]) -> &mut [u8] { - let len = x.len() * 4; - unsafe { slice::from_raw_parts_mut(x.as_mut_ptr() as _, len) } -} diff --git a/src/consts.rs b/src/consts.rs index fee2d01a..18502bd1 100644 --- a/src/consts.rs +++ b/src/consts.rs @@ -109,6 +109,50 @@ pub(crate) const READ: bool = false; pub(crate) const INC_ADDR: bool = true; pub(crate) const FIXED_ADDR: bool = false; +#[allow(non_camel_case_types)] +#[derive(Copy, Clone)] +#[repr(u8)] +pub enum EStatus { + /// operation was successful + SUCCESS = 0, + /// operation failed + FAIL = 1, + /// operation timed out + TIMEOUT = 2, + /// failed due to no matching network found + NO_NETWORKS = 3, + /// operation was aborted + ABORT = 4, + /// protocol failure: packet not ack'd + NO_ACK = 5, + /// AUTH or ASSOC packet was unsolicited + UNSOLICITED = 6, + /// attempt to assoc to an auto auth configuration + ATTEMPT = 7, + /// scan results are incomplete + PARTIAL = 8, + /// scan aborted by another scan + NEWSCAN = 9, + /// scan aborted due to assoc in progress + NEWASSOC = 10, + /// 802.11h quiet period started + _11HQUIET = 11, + /// user disabled scanning (WLC_SET_SCANSUPPRESS) + SUPPRESS = 12, + /// no allowable channels to scan + NOCHANS = 13, + /// scan aborted due to CCX fast roam + CCXFASTRM = 14, + /// abort channel select + CS_ABORT = 15, +} + +impl PartialEq for u32 { + fn eq(&self, other: &EStatus) -> bool { + *self == *other as Self + } +} + #[allow(dead_code)] pub(crate) struct FormatStatus(pub u32); diff --git a/src/control.rs b/src/control.rs index 30d5d092..824c5512 100644 --- a/src/control.rs +++ b/src/control.rs @@ -6,7 +6,7 @@ use embassy_time::{Duration, Timer}; pub use crate::bus::SpiBusCyw43; use crate::consts::*; -use crate::events::{Event, EventQueue}; +use crate::events::{Event, Events}; use crate::fmt::Bytes; use crate::ioctl::{IoctlState, IoctlType}; use crate::structs::*; @@ -14,15 +14,15 @@ use crate::{countries, PowerManagementMode}; pub struct Control<'a> { state_ch: ch::StateRunner<'a>, - event_sub: &'a EventQueue, + events: &'a Events, ioctl_state: &'a IoctlState, } impl<'a> Control<'a> { - pub(crate) fn new(state_ch: ch::StateRunner<'a>, event_sub: &'a EventQueue, ioctl_state: &'a IoctlState) -> Self { + pub(crate) fn new(state_ch: ch::StateRunner<'a>, event_sub: &'a Events, ioctl_state: &'a IoctlState) -> Self { Self { state_ch, - event_sub, + events: event_sub, ioctl_state, } } @@ -195,24 +195,25 @@ impl<'a> Control<'a> { } async fn wait_for_join(&mut self, i: SsidInfo) { - let mut subscriber = self.event_sub.subscriber().unwrap(); + self.events.mask.enable(&[Event::JOIN, Event::AUTH]); + let mut subscriber = self.events.queue.subscriber().unwrap(); self.ioctl(IoctlType::Set, IOCTL_CMD_SET_SSID, 0, &mut i.to_bytes()) .await; // set_ssid loop { let msg = subscriber.next_message_pure().await; - if msg.event_type == Event::AUTH && msg.status != 0 { + if msg.header.event_type == Event::AUTH && msg.header.status != 0 { // retry - warn!("JOIN failed with status={}", msg.status); + warn!("JOIN failed with status={}", msg.header.status); self.ioctl(IoctlType::Set, IOCTL_CMD_SET_SSID, 0, &mut i.to_bytes()) .await; - } else if msg.event_type == Event::JOIN && msg.status == 0 { + } else if msg.header.event_type == Event::JOIN && msg.header.status == 0 { // successful join break; } } - + self.events.mask.disable_all(); self.state_ch.set_link_state(LinkState::Up); info!("JOINED"); } diff --git a/src/events.rs b/src/events.rs index 87f6c01a..fbdfbc88 100644 --- a/src/events.rs +++ b/src/events.rs @@ -1,7 +1,7 @@ #![allow(unused)] #![allow(non_camel_case_types)] -use core::num; +use core::cell::RefCell; use embassy_sync::blocking_mutex::raw::NoopRawMutex; use embassy_sync::pubsub::{PubSubChannel, Publisher, Subscriber}; @@ -284,13 +284,114 @@ pub enum Event { LAST = 190, } -pub type EventQueue = PubSubChannel; -pub type EventPublisher<'a> = Publisher<'a, NoopRawMutex, EventStatus, 2, 1, 1>; -pub type EventSubscriber<'a> = Subscriber<'a, NoopRawMutex, EventStatus, 2, 1, 1>; +// TODO this PubSub can probably be replaced with shared memory to make it a bit more efficient. +pub type EventQueue = PubSubChannel; +pub type EventPublisher<'a> = Publisher<'a, NoopRawMutex, Message, 2, 1, 1>; +pub type EventSubscriber<'a> = Subscriber<'a, NoopRawMutex, Message, 2, 1, 1>; + +pub struct Events { + pub queue: EventQueue, + pub mask: SharedEventMask, +} + +impl Events { + pub fn new() -> Self { + Self { + queue: EventQueue::new(), + mask: SharedEventMask::default(), + } + } +} #[derive(Clone, Copy)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct EventStatus { +pub struct Status { pub event_type: Event, pub status: u32, } + +#[derive(Clone, Copy)] +pub enum Payload { + None, +} + +#[derive(Clone, Copy)] + +pub struct Message { + pub header: Status, + pub payload: Payload, +} + +impl Message { + pub fn new(status: Status, payload: Payload) -> Self { + Self { + header: status, + payload, + } + } +} + +const EVENT_BITS: usize = ((Event::LAST as usize + 31) & !31) / 32; + +#[derive(Default)] +struct EventMask { + mask: [u32; EVENT_BITS], +} + +impl EventMask { + fn enable(&mut self, event: Event) { + let n = event as u32; + let word = n >> 5; + let bit = n & 0b11111; + + self.mask[word as usize] |= (1 << bit); + } + + fn disable(&mut self, event: Event) { + let n = event as u32; + let word = n >> 5; + let bit = n & 0b11111; + + self.mask[word as usize] &= !(1 << bit); + } + + fn is_enabled(&self, event: Event) -> bool { + let n = event as u32; + let word = n >> 5; + let bit = n & 0b11111; + + self.mask[word as usize] & (1 << bit) > 0 + } +} + +#[derive(Default)] + +pub struct SharedEventMask { + mask: RefCell, +} + +impl SharedEventMask { + pub fn enable(&self, events: &[Event]) { + let mut mask = self.mask.borrow_mut(); + for event in events { + mask.enable(*event); + } + } + + pub fn disable(&self, events: &[Event]) { + let mut mask = self.mask.borrow_mut(); + for event in events { + mask.disable(*event); + } + } + + pub fn disable_all(&self) { + let mut mask = self.mask.borrow_mut(); + mask.mask = Default::default(); + } + + pub fn is_enabled(&self, event: Event) -> bool { + let mask = self.mask.borrow(); + mask.is_enabled(event) + } +} diff --git a/src/ioctl.rs b/src/ioctl.rs index 89b20a2d..803934cf 100644 --- a/src/ioctl.rs +++ b/src/ioctl.rs @@ -4,6 +4,8 @@ use core::task::{Poll, Waker}; use embassy_sync::waitqueue::WakerRegistration; +use crate::fmt::Bytes; + #[derive(Clone, Copy)] pub enum IoctlType { Get = 0, @@ -100,6 +102,8 @@ impl IoctlState { pub fn ioctl_done(&self, response: &[u8]) { if let IoctlStateInner::Sent { buf } = self.state.get() { + info!("IOCTL Response: {:02x}", Bytes(response)); + // TODO fix this (unsafe { &mut *buf }[..response.len()]).copy_from_slice(response); @@ -107,6 +111,8 @@ impl IoctlState { resp_len: response.len(), }); self.wake_control(); + } else { + warn!("IOCTL Response but no pending Ioctl"); } } } diff --git a/src/lib.rs b/src/lib.rs index 069ca40f..f9244bdd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -18,9 +18,11 @@ mod control; mod nvram; mod runner; +use core::slice; + use embassy_net_driver_channel as ch; use embedded_hal_1::digital::OutputPin; -use events::EventQueue; +use events::Events; use ioctl::IoctlState; use crate::bus::Bus; @@ -103,7 +105,7 @@ const CHIP: Chip = Chip { pub struct State { ioctl_state: IoctlState, ch: ch::State, - events: EventQueue, + events: Events, } impl State { @@ -111,7 +113,7 @@ impl State { Self { ioctl_state: IoctlState::new(), ch: ch::State::new(), - events: EventQueue::new(), + events: Events::new(), } } } @@ -225,3 +227,8 @@ where runner, ) } + +fn slice8_mut(x: &mut [u32]) -> &mut [u8] { + let len = x.len() * 4; + unsafe { slice::from_raw_parts_mut(x.as_mut_ptr() as _, len) } +} diff --git a/src/runner.rs b/src/runner.rs index f0f6fcee..554a711d 100644 --- a/src/runner.rs +++ b/src/runner.rs @@ -1,5 +1,3 @@ -use core::slice; - use embassy_futures::select::{select3, Either3}; use embassy_net_driver_channel as ch; use embassy_sync::pubsub::PubSubBehavior; @@ -9,12 +7,12 @@ use embedded_hal_1::digital::OutputPin; use crate::bus::Bus; pub use crate::bus::SpiBusCyw43; use crate::consts::*; -use crate::events::{EventQueue, EventStatus}; +use crate::events::{Events, Status}; use crate::fmt::Bytes; use crate::ioctl::{IoctlState, IoctlType, PendingIoctl}; use crate::nvram::NVRAM; use crate::structs::*; -use crate::{events, Core, CHIP, MTU}; +use crate::{events, slice8_mut, Core, CHIP, MTU}; #[cfg(feature = "firmware-logs")] struct LogState { @@ -45,7 +43,7 @@ pub struct Runner<'a, PWR, SPI> { sdpcm_seq: u8, sdpcm_seq_max: u8, - events: &'a EventQueue, + events: &'a Events, #[cfg(feature = "firmware-logs")] log: LogState, @@ -60,7 +58,7 @@ where ch: ch::Runner<'a, MTU>, bus: Bus, ioctl_state: &'a IoctlState, - events: &'a EventQueue, + events: &'a Events, ) -> Self { Self { ch, @@ -353,8 +351,6 @@ where panic!("IOCTL error {}", cdc_header.status as i32); } - info!("IOCTL Response: {:02x}", Bytes(response)); - self.ioctl_state.ioctl_done(response); } } @@ -406,11 +402,17 @@ where Bytes(evt_data) ); - if evt_type == events::Event::AUTH || evt_type == events::Event::JOIN { - self.events.publish_immediate(EventStatus { - status: event_packet.msg.status, - event_type: evt_type, - }); + if self.events.mask.is_enabled(evt_type) { + let status = event_packet.msg.status; + let event_payload = events::Payload::None; + + self.events.queue.publish_immediate(events::Message::new( + Status { + event_type: evt_type, + status, + }, + event_payload, + )); } } CHANNEL_TYPE_DATA => { @@ -548,8 +550,3 @@ where true } } - -fn slice8_mut(x: &mut [u32]) -> &mut [u8] { - let len = x.len() * 4; - unsafe { slice::from_raw_parts_mut(x.as_mut_ptr() as _, len) } -} From 582a15a69320d987de5db3121af2f805f8508a6b Mon Sep 17 00:00:00 2001 From: kbleeke Date: Tue, 25 Apr 2023 18:38:17 +0200 Subject: [PATCH 101/178] cleanup EventMask --- src/events.rs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/events.rs b/src/events.rs index fbdfbc88..d6f114ed 100644 --- a/src/events.rs +++ b/src/events.rs @@ -331,34 +331,34 @@ impl Message { } } -const EVENT_BITS: usize = ((Event::LAST as usize + 31) & !31) / 32; - #[derive(Default)] struct EventMask { - mask: [u32; EVENT_BITS], + mask: [u32; Self::WORD_COUNT], } impl EventMask { + const WORD_COUNT: usize = ((Event::LAST as u32 + (u32::BITS - 1)) / u32::BITS) as usize; + fn enable(&mut self, event: Event) { let n = event as u32; - let word = n >> 5; - let bit = n & 0b11111; + let word = n / u32::BITS; + let bit = n % u32::BITS; self.mask[word as usize] |= (1 << bit); } fn disable(&mut self, event: Event) { let n = event as u32; - let word = n >> 5; - let bit = n & 0b11111; + let word = n / u32::BITS; + let bit = n % u32::BITS; self.mask[word as usize] &= !(1 << bit); } fn is_enabled(&self, event: Event) -> bool { let n = event as u32; - let word = n >> 5; - let bit = n & 0b11111; + let word = n / u32::BITS; + let bit = n % u32::BITS; self.mask[word as usize] & (1 << bit) > 0 } From 9e96655757180d7fe32ebff1ed93a35a4c3cff28 Mon Sep 17 00:00:00 2001 From: kbleeke Date: Tue, 25 Apr 2023 19:08:47 +0200 Subject: [PATCH 102/178] comment some choices for current event handling --- src/control.rs | 6 ++++-- src/runner.rs | 4 ++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/control.rs b/src/control.rs index 824c5512..0c06009b 100644 --- a/src/control.rs +++ b/src/control.rs @@ -197,18 +197,20 @@ impl<'a> Control<'a> { async fn wait_for_join(&mut self, i: SsidInfo) { self.events.mask.enable(&[Event::JOIN, Event::AUTH]); let mut subscriber = self.events.queue.subscriber().unwrap(); + // the actual join operation starts here + // we make sure to enable events before so we don't miss any self.ioctl(IoctlType::Set, IOCTL_CMD_SET_SSID, 0, &mut i.to_bytes()) .await; // set_ssid loop { let msg = subscriber.next_message_pure().await; - if msg.header.event_type == Event::AUTH && msg.header.status != 0 { + if msg.header.event_type == Event::AUTH && msg.header.status != EStatus::SUCCESS { // retry warn!("JOIN failed with status={}", msg.header.status); self.ioctl(IoctlType::Set, IOCTL_CMD_SET_SSID, 0, &mut i.to_bytes()) .await; - } else if msg.header.event_type == Event::JOIN && msg.header.status == 0 { + } else if msg.header.event_type == Event::JOIN && msg.header.status == EStatus::SUCCESS { // successful join break; } diff --git a/src/runner.rs b/src/runner.rs index 554a711d..806ddfc4 100644 --- a/src/runner.rs +++ b/src/runner.rs @@ -406,6 +406,10 @@ where let status = event_packet.msg.status; let event_payload = events::Payload::None; + // this intentionally uses the non-blocking publish immediate + // publish() is a deadlock risk in the current design as awaiting here prevents ioctls + // The `Runner` always yields when accessing the device, so consumers always have a chance to receive the event + // (if they are actively awaiting the queue) self.events.queue.publish_immediate(events::Message::new( Status { event_type: evt_type, From 123c11042719648265d22f0d42b9f9e0ff8d73fc Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Wed, 26 Apr 2023 16:19:23 +0200 Subject: [PATCH 103/178] Revert "Workaround regex breaking change." This reverts commit 6a1a3e6877053b1b72adb3c1446f4f077ad3b03e. --- examples/rpi-pico-w/Cargo.toml | 3 --- 1 file changed, 3 deletions(-) diff --git a/examples/rpi-pico-w/Cargo.toml b/examples/rpi-pico-w/Cargo.toml index d211f926..dca796e3 100644 --- a/examples/rpi-pico-w/Cargo.toml +++ b/examples/rpi-pico-w/Cargo.toml @@ -25,9 +25,6 @@ futures = { version = "0.3.17", default-features = false, features = ["async-awa embedded-io = { version = "0.4.0", features = ["async", "defmt"] } heapless = "0.7.15" -[build-dependencies] -# Workaround https://github.com/embassy-rs/cyw43/issues/68 -regex = { version = "~1.7.3", default-features = false } [patch.crates-io] embassy-executor = { git = "https://github.com/embassy-rs/embassy", rev = "047ea9066f0d946fd4d706577b21df38fd3b1647" } From 0c7ce803849779af2ac6a2d3df401f5dafd57323 Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Wed, 26 Apr 2023 16:20:23 +0200 Subject: [PATCH 104/178] Fix missing defmt impl. --- src/structs.rs | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/src/structs.rs b/src/structs.rs index 6d5d31b0..f54ec7fc 100644 --- a/src/structs.rs +++ b/src/structs.rs @@ -115,7 +115,6 @@ impl SdpcmHeader { } #[derive(Debug, Clone, Copy)] -// #[cfg_attr(feature = "defmt", derive(defmt::Format))] #[repr(C, packed(2))] pub struct CdcHeader { pub cmd: u32, @@ -126,6 +125,25 @@ pub struct CdcHeader { } impl_bytes!(CdcHeader); +#[cfg(feature = "defmt")] +impl defmt::Format for CdcHeader { + fn format(&self, fmt: defmt::Formatter) { + fn copy(t: T) -> T { + t + } + + defmt::write!( + fmt, + "CdcHeader{{cmd: {=u32:08x}, len: {=u32:08x}, flags: {=u16:04x}, id: {=u16:04x}, status: {=u32:08x}}}", + copy(self.cmd), + copy(self.len), + copy(self.flags), + copy(self.id), + copy(self.status), + ) + } +} + impl CdcHeader { pub fn parse(packet: &mut [u8]) -> Option<(&mut Self, &mut [u8])> { if packet.len() < Self::SIZE { From 0a2d6f0be069233bdfa9d9eee6f41184fdda72f3 Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Wed, 26 Apr 2023 16:20:49 +0200 Subject: [PATCH 105/178] ci: build with DEFMT_LOG=trace to catch all defmt issues. --- .vscode/settings.json | 1 - ci.sh | 2 ++ 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 34430769..dd479929 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -9,7 +9,6 @@ "rust-analyzer.check.noDefaultFeatures": true, "rust-analyzer.linkedProjects": [ "examples/rpi-pico-w/Cargo.toml", - "cyw43-pio/Cargo.toml", ], "rust-analyzer.server.extraEnv": { "WIFI_NETWORK": "foo", diff --git a/ci.sh b/ci.sh index 1b33564f..d41b3aa8 100755 --- a/ci.sh +++ b/ci.sh @@ -2,6 +2,8 @@ set -euxo pipefail +export DEFMT_LOG=trace + # build examples #================== From 0c8e5f92c7ebe6fd148a986baaaa6ac746d939c2 Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Wed, 26 Apr 2023 18:10:39 +0200 Subject: [PATCH 106/178] Switch from probe-run to probe-rs-cli. --- README.md | 2 +- examples/rpi-pico-w/.cargo/config.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index d24ec82f..d0920aae 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ TODO: ## Running the example -- `cargo install probe-run` +- `cargo install probe-rs-cli` - `cd examples/rpi-pico-w` - `WIFI_NETWORK=MyWifiNetwork WIFI_PASSWORD=MyWifiPassword cargo run --release` diff --git a/examples/rpi-pico-w/.cargo/config.toml b/examples/rpi-pico-w/.cargo/config.toml index 6183e70f..f1ed8af9 100644 --- a/examples/rpi-pico-w/.cargo/config.toml +++ b/examples/rpi-pico-w/.cargo/config.toml @@ -1,5 +1,5 @@ [target.'cfg(all(target_arch = "arm", target_os = "none"))'] -runner = "probe-run --chip RP2040" +runner = "probe-rs-cli run --chip RP2040" [build] target = "thumbv6m-none-eabi" From 4d551a586589b9fd8b5f92d2f98b90be4144154e Mon Sep 17 00:00:00 2001 From: kalkyl Date: Thu, 27 Apr 2023 19:37:19 +0200 Subject: [PATCH 107/178] Update embassy --- Cargo.toml | 12 ++++++------ cyw43-pio/Cargo.toml | 2 +- examples/rpi-pico-w/Cargo.toml | 18 +++++++++--------- rust-toolchain.toml | 2 +- src/ioctl.rs | 10 +++++++++- 5 files changed, 26 insertions(+), 18 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 4ea5d8b6..c4872f42 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,7 +20,7 @@ atomic-polyfill = "0.1.5" defmt = { version = "0.3", optional = true } log = { version = "0.4.17", optional = true } -cortex-m = "0.7.3" +cortex-m = "0.7.6" cortex-m-rt = "0.7.0" futures = { version = "0.3.17", default-features = false, features = ["async-await", "cfg-target-has-atomic", "unstable"] } @@ -28,11 +28,11 @@ embedded-hal-1 = { package = "embedded-hal", version = "1.0.0-alpha.10" } num_enum = { version = "0.5.7", default-features = false } [patch.crates-io] -embassy-time = { git = "https://github.com/embassy-rs/embassy", rev = "047ea9066f0d946fd4d706577b21df38fd3b1647" } -embassy-futures = { git = "https://github.com/embassy-rs/embassy", rev = "047ea9066f0d946fd4d706577b21df38fd3b1647" } -embassy-sync = { git = "https://github.com/embassy-rs/embassy", rev = "047ea9066f0d946fd4d706577b21df38fd3b1647" } -embassy-net-driver-channel = { git = "https://github.com/embassy-rs/embassy", rev = "047ea9066f0d946fd4d706577b21df38fd3b1647" } -embassy-rp = { git = "https://github.com/embassy-rs/embassy", rev = "047ea9066f0d946fd4d706577b21df38fd3b1647" } +embassy-time = { git = "https://github.com/embassy-rs/embassy", rev = "03d6363d5af5dcaf21b52734994a466ca593d2b6" } +embassy-futures = { git = "https://github.com/embassy-rs/embassy", rev = "03d6363d5af5dcaf21b52734994a466ca593d2b6" } +embassy-sync = { git = "https://github.com/embassy-rs/embassy", rev = "03d6363d5af5dcaf21b52734994a466ca593d2b6" } +embassy-net-driver-channel = { git = "https://github.com/embassy-rs/embassy", rev = "03d6363d5af5dcaf21b52734994a466ca593d2b6" } +embassy-rp = { git = "https://github.com/embassy-rs/embassy", rev = "03d6363d5af5dcaf21b52734994a466ca593d2b6" } [workspace] members = ["cyw43-pio"] diff --git a/cyw43-pio/Cargo.toml b/cyw43-pio/Cargo.toml index 2fc6b759..4ca227d3 100644 --- a/cyw43-pio/Cargo.toml +++ b/cyw43-pio/Cargo.toml @@ -7,7 +7,7 @@ edition = "2021" [dependencies] cyw43 = { path = "../" } -embassy-rp = { version = "0.1.0", features = ["defmt", "unstable-traits", "nightly", "unstable-pac", "time-driver", "pio"] } +embassy-rp = { version = "0.1.0", features = ["defmt", "unstable-traits", "nightly", "unstable-pac", "time-driver"] } pio-proc = "0.2" pio = "0.2.1" defmt = "0.3" \ No newline at end of file diff --git a/examples/rpi-pico-w/Cargo.toml b/examples/rpi-pico-w/Cargo.toml index dca796e3..970db089 100644 --- a/examples/rpi-pico-w/Cargo.toml +++ b/examples/rpi-pico-w/Cargo.toml @@ -9,7 +9,7 @@ cyw43 = { path = "../../", features = ["defmt", "firmware-logs"] } cyw43-pio = { path = "../../cyw43-pio" } embassy-executor = { version = "0.1.0", features = ["defmt", "integrated-timers", "executor-thread", "arch-cortex-m"] } embassy-time = { version = "0.1.0", features = ["defmt", "defmt-timestamp-uptime"] } -embassy-rp = { version = "0.1.0", features = ["defmt", "unstable-traits", "nightly", "unstable-pac", "time-driver", "pio"] } +embassy-rp = { version = "0.1.0", features = ["defmt", "unstable-traits", "nightly", "unstable-pac", "time-driver"] } embassy-net = { version = "0.1.0", features = ["defmt", "tcp", "dhcpv4", "medium-ethernet", "unstable-traits", "nightly"] } atomic-polyfill = "0.1.5" static_cell = "1.0" @@ -27,14 +27,14 @@ heapless = "0.7.15" [patch.crates-io] -embassy-executor = { git = "https://github.com/embassy-rs/embassy", rev = "047ea9066f0d946fd4d706577b21df38fd3b1647" } -embassy-time = { git = "https://github.com/embassy-rs/embassy", rev = "047ea9066f0d946fd4d706577b21df38fd3b1647" } -embassy-futures = { git = "https://github.com/embassy-rs/embassy", rev = "047ea9066f0d946fd4d706577b21df38fd3b1647" } -embassy-sync = { git = "https://github.com/embassy-rs/embassy", rev = "047ea9066f0d946fd4d706577b21df38fd3b1647" } -embassy-rp = { git = "https://github.com/embassy-rs/embassy", rev = "047ea9066f0d946fd4d706577b21df38fd3b1647" } -embassy-net = { git = "https://github.com/embassy-rs/embassy", rev = "047ea9066f0d946fd4d706577b21df38fd3b1647" } -embassy-net-driver = { git = "https://github.com/embassy-rs/embassy", rev = "047ea9066f0d946fd4d706577b21df38fd3b1647" } -embassy-net-driver-channel = { git = "https://github.com/embassy-rs/embassy", rev = "047ea9066f0d946fd4d706577b21df38fd3b1647" } +embassy-executor = { git = "https://github.com/embassy-rs/embassy", rev = "03d6363d5af5dcaf21b52734994a466ca593d2b6" } +embassy-time = { git = "https://github.com/embassy-rs/embassy", rev = "03d6363d5af5dcaf21b52734994a466ca593d2b6" } +embassy-futures = { git = "https://github.com/embassy-rs/embassy", rev = "03d6363d5af5dcaf21b52734994a466ca593d2b6" } +embassy-sync = { git = "https://github.com/embassy-rs/embassy", rev = "03d6363d5af5dcaf21b52734994a466ca593d2b6" } +embassy-rp = { git = "https://github.com/embassy-rs/embassy", rev = "03d6363d5af5dcaf21b52734994a466ca593d2b6" } +embassy-net = { git = "https://github.com/embassy-rs/embassy", rev = "03d6363d5af5dcaf21b52734994a466ca593d2b6" } +embassy-net-driver = { git = "https://github.com/embassy-rs/embassy", rev = "03d6363d5af5dcaf21b52734994a466ca593d2b6" } +embassy-net-driver-channel = { git = "https://github.com/embassy-rs/embassy", rev = "03d6363d5af5dcaf21b52734994a466ca593d2b6" } [profile.dev] debug = 2 diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 88519979..2582e88f 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,7 +1,7 @@ # Before upgrading check that everything is available on all tier1 targets here: # https://rust-lang.github.io/rustup-components-history [toolchain] -channel = "nightly-2023-04-02" +channel = "nightly-2023-04-18" components = [ "rust-src", "rustfmt" ] targets = [ "thumbv6m-none-eabi", diff --git a/src/ioctl.rs b/src/ioctl.rs index 89b20a2d..0fee1ad1 100644 --- a/src/ioctl.rs +++ b/src/ioctl.rs @@ -25,12 +25,20 @@ enum IoctlStateInner { Done { resp_len: usize }, } -#[derive(Default)] struct Wakers { control: WakerRegistration, runner: WakerRegistration, } +impl Default for Wakers { + fn default() -> Self { + Self { + control: WakerRegistration::new(), + runner: WakerRegistration::new(), + } + } +} + pub struct IoctlState { state: Cell, wakers: RefCell, From 2c5d94493c25792435102680fe8e659cc7dad9df Mon Sep 17 00:00:00 2001 From: kbleeke Date: Thu, 30 Mar 2023 17:05:29 +0200 Subject: [PATCH 108/178] wifi scan ioctl --- examples/rpi-pico-w/src/main.rs | 1 - src/control.rs | 67 +++++++++++++++++++++++++-- src/events.rs | 11 +++-- src/lib.rs | 1 + src/runner.rs | 14 +++++- src/structs.rs | 81 +++++++++++++++++++++++++++++++++ 6 files changed, 165 insertions(+), 10 deletions(-) diff --git a/examples/rpi-pico-w/src/main.rs b/examples/rpi-pico-w/src/main.rs index d075aec2..944beaac 100644 --- a/examples/rpi-pico-w/src/main.rs +++ b/examples/rpi-pico-w/src/main.rs @@ -143,4 +143,3 @@ async fn main(spawner: Spawner) { } } } - diff --git a/src/control.rs b/src/control.rs index 0c06009b..bcb44937 100644 --- a/src/control.rs +++ b/src/control.rs @@ -6,11 +6,11 @@ use embassy_time::{Duration, Timer}; pub use crate::bus::SpiBusCyw43; use crate::consts::*; -use crate::events::{Event, Events}; +use crate::events::{Event, EventSubscriber, Events}; use crate::fmt::Bytes; use crate::ioctl::{IoctlState, IoctlType}; use crate::structs::*; -use crate::{countries, PowerManagementMode}; +use crate::{countries, events, PowerManagementMode}; pub struct Control<'a> { state_ch: ch::StateRunner<'a>, @@ -245,9 +245,13 @@ impl<'a> Control<'a> { } async fn set_iovar(&mut self, name: &str, val: &[u8]) { + self.set_iovar_v::<64>(name, val).await + } + + async fn set_iovar_v(&mut self, name: &str, val: &[u8]) { info!("set {} = {:02x}", name, Bytes(val)); - let mut buf = [0; 64]; + let mut buf = [0; BUFSIZE]; buf[..name.len()].copy_from_slice(name.as_bytes()); buf[name.len()] = 0; buf[name.len() + 1..][..val.len()].copy_from_slice(val); @@ -304,4 +308,61 @@ impl<'a> Control<'a> { resp_len } + + pub async fn scan(&mut self) -> Scanner<'_> { + const SCANTYPE_PASSIVE: u8 = 1; + + let scan_params = ScanParams { + version: 1, + action: 1, + sync_id: 1, + ssid_len: 0, + ssid: [0; 32], + bssid: [0xff; 6], + bss_type: 2, + scan_type: SCANTYPE_PASSIVE, + nprobes: !0, + active_time: !0, + passive_time: !0, + home_time: !0, + channel_num: 0, + channel_list: [0; 1], + }; + + self.events.mask.enable(&[Event::ESCAN_RESULT]); + let subscriber = self.events.queue.subscriber().unwrap(); + self.set_iovar_v::<256>("escan", &scan_params.to_bytes()).await; + + Scanner { + subscriber, + events: &self.events, + } + } +} + +pub struct Scanner<'a> { + subscriber: EventSubscriber<'a>, + events: &'a Events, +} + +impl Scanner<'_> { + pub async fn next(&mut self) -> Option { + let event = self.subscriber.next_message_pure().await; + if event.header.status != EStatus::PARTIAL { + self.events.mask.disable_all(); + return None; + } + + if let events::Payload::BssInfo(bss) = event.payload { + Some(bss) + } else { + None + } + } +} + +impl Drop for Scanner<'_> { + fn drop(&mut self) { + self.events.mask.disable_all(); + } } diff --git a/src/events.rs b/src/events.rs index d6f114ed..a94c49a0 100644 --- a/src/events.rs +++ b/src/events.rs @@ -1,10 +1,12 @@ -#![allow(unused)] +#![allow(dead_code)] #![allow(non_camel_case_types)] use core::cell::RefCell; use embassy_sync::blocking_mutex::raw::NoopRawMutex; -use embassy_sync::pubsub::{PubSubChannel, Publisher, Subscriber}; +use embassy_sync::pubsub::{PubSubChannel, Subscriber}; + +use crate::structs::BssInfo; #[derive(Debug, Clone, Copy, PartialEq, Eq, num_enum::FromPrimitive)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] @@ -286,7 +288,6 @@ pub enum Event { // TODO this PubSub can probably be replaced with shared memory to make it a bit more efficient. pub type EventQueue = PubSubChannel; -pub type EventPublisher<'a> = Publisher<'a, NoopRawMutex, Message, 2, 1, 1>; pub type EventSubscriber<'a> = Subscriber<'a, NoopRawMutex, Message, 2, 1, 1>; pub struct Events { @@ -313,6 +314,7 @@ pub struct Status { #[derive(Clone, Copy)] pub enum Payload { None, + BssInfo(BssInfo), } #[derive(Clone, Copy)] @@ -344,7 +346,7 @@ impl EventMask { let word = n / u32::BITS; let bit = n % u32::BITS; - self.mask[word as usize] |= (1 << bit); + self.mask[word as usize] |= 1 << bit; } fn disable(&mut self, event: Event) { @@ -378,6 +380,7 @@ impl SharedEventMask { } } + #[allow(dead_code)] pub fn disable(&self, events: &[Event]) { let mut mask = self.mask.borrow_mut(); for event in events { diff --git a/src/lib.rs b/src/lib.rs index f9244bdd..d437a882 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -29,6 +29,7 @@ use crate::bus::Bus; pub use crate::bus::SpiBusCyw43; pub use crate::control::Control; pub use crate::runner::Runner; +pub use crate::structs::BssInfo; const MTU: usize = 1514; diff --git a/src/runner.rs b/src/runner.rs index 806ddfc4..9b99e174 100644 --- a/src/runner.rs +++ b/src/runner.rs @@ -7,7 +7,7 @@ use embedded_hal_1::digital::OutputPin; use crate::bus::Bus; pub use crate::bus::SpiBusCyw43; use crate::consts::*; -use crate::events::{Events, Status}; +use crate::events::{Event, Events, Status}; use crate::fmt::Bytes; use crate::ioctl::{IoctlState, IoctlType, PendingIoctl}; use crate::nvram::NVRAM; @@ -351,6 +351,8 @@ where panic!("IOCTL error {}", cdc_header.status as i32); } + info!("IOCTL Response: {:02x}", Bytes(response)); + self.ioctl_state.ioctl_done(response); } } @@ -404,7 +406,15 @@ where if self.events.mask.is_enabled(evt_type) { let status = event_packet.msg.status; - let event_payload = events::Payload::None; + let event_payload = match evt_type { + Event::ESCAN_RESULT if status == EStatus::PARTIAL => { + let Some((_, bss_info)) = ScanResults::parse(evt_data) else { return }; + let Some(bss_info) = BssInfo::parse(bss_info) else { return }; + events::Payload::BssInfo(*bss_info) + } + Event::ESCAN_RESULT => events::Payload::None, + _ => events::Payload::None, + }; // this intentionally uses the non-blocking publish immediate // publish() is a deadlock risk in the current design as awaiting here prevents ioctls diff --git a/src/structs.rs b/src/structs.rs index f54ec7fc..d01d5a65 100644 --- a/src/structs.rs +++ b/src/structs.rs @@ -404,3 +404,84 @@ impl EventMask { self.events[evt / 8] &= !(1 << (evt % 8)); } } + +/// Parameters for a wifi scan +#[derive(Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[repr(C)] +pub struct ScanParams { + pub version: u32, + pub action: u16, + pub sync_id: u16, + pub ssid_len: u32, + pub ssid: [u8; 32], + pub bssid: [u8; 6], + pub bss_type: u8, + pub scan_type: u8, + pub nprobes: u32, + pub active_time: u32, + pub passive_time: u32, + pub home_time: u32, + pub channel_num: u32, + pub channel_list: [u16; 1], +} +impl_bytes!(ScanParams); + +/// Wifi Scan Results Header, followed by `bss_count` `BssInfo` +#[derive(Clone, Copy)] +// #[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[repr(C, packed(2))] +pub struct ScanResults { + pub buflen: u32, + pub version: u32, + pub sync_id: u16, + pub bss_count: u16, +} +impl_bytes!(ScanResults); + +impl ScanResults { + pub fn parse(packet: &mut [u8]) -> Option<(&mut ScanResults, &mut [u8])> { + if packet.len() < ScanResults::SIZE { + return None; + } + + let (scan_results, bssinfo) = packet.split_at_mut(ScanResults::SIZE); + let scan_results = ScanResults::from_bytes_mut(scan_results.try_into().unwrap()); + + if scan_results.bss_count > 0 && bssinfo.len() < BssInfo::SIZE { + warn!("Scan result, incomplete BssInfo"); + return None; + } + + Some((scan_results, bssinfo)) + } +} + +/// Wifi Scan Result +#[derive(Clone, Copy)] +// #[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[repr(C, packed(2))] +#[non_exhaustive] +pub struct BssInfo { + pub version: u32, + pub length: u32, + pub bssid: [u8; 6], + pub beacon_period: u16, + pub capability: u16, + pub ssid_len: u8, + pub ssid: [u8; 32], + // there will be more stuff here +} +impl_bytes!(BssInfo); + +impl BssInfo { + pub fn parse(packet: &mut [u8]) -> Option<&mut Self> { + if packet.len() < BssInfo::SIZE { + return None; + } + + Some(BssInfo::from_bytes_mut( + packet[..BssInfo::SIZE].as_mut().try_into().unwrap(), + )) + } +} From 76b967a966677e570cc0a2942ed3ccd29b2d1017 Mon Sep 17 00:00:00 2001 From: kbleeke Date: Fri, 28 Apr 2023 21:17:13 +0200 Subject: [PATCH 109/178] comment wifi scanning items --- src/control.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/control.rs b/src/control.rs index bcb44937..934bade2 100644 --- a/src/control.rs +++ b/src/control.rs @@ -309,6 +309,13 @@ impl<'a> Control<'a> { resp_len } + /// Start a wifi scan + /// + /// Returns a `Stream` of networks found by the device + /// + /// # Note + /// Device events are currently implemented using a bounded queue. + /// To not miss any events, you should make sure to always await the stream. pub async fn scan(&mut self) -> Scanner<'_> { const SCANTYPE_PASSIVE: u8 = 1; @@ -346,6 +353,7 @@ pub struct Scanner<'a> { } impl Scanner<'_> { + /// wait for the next found network pub async fn next(&mut self) -> Option { let event = self.subscriber.next_message_pure().await; if event.header.status != EStatus::PARTIAL { From 099ec7443bed2183397005b4e8ebfcd2492e2b4c Mon Sep 17 00:00:00 2001 From: Satoshi Tanaka Date: Mon, 1 May 2023 04:30:21 +0900 Subject: [PATCH 110/178] Add AP mode (unencrypted) --- src/consts.rs | 3 +++ src/control.rs | 37 +++++++++++++++++++++++++++++++++++++ src/structs.rs | 9 +++++++++ 3 files changed, 49 insertions(+) diff --git a/src/consts.rs b/src/consts.rs index 18502bd1..ade3cb2c 100644 --- a/src/consts.rs +++ b/src/consts.rs @@ -93,8 +93,11 @@ pub(crate) const IRQ_F2_INTR: u16 = 0x4000; pub(crate) const IRQ_F3_INTR: u16 = 0x8000; pub(crate) const IOCTL_CMD_UP: u32 = 2; +pub(crate) const IOCTL_CMD_DOWN: u32 = 3; pub(crate) const IOCTL_CMD_SET_SSID: u32 = 26; +pub(crate) const IOCTL_CMD_SET_CHANNEL: u32 = 30; pub(crate) const IOCTL_CMD_ANTDIV: u32 = 64; +pub(crate) const IOCTL_CMD_SET_AP: u32 = 118; pub(crate) const IOCTL_CMD_SET_VAR: u32 = 263; pub(crate) const IOCTL_CMD_GET_VAR: u32 = 262; pub(crate) const IOCTL_CMD_SET_PASSPHRASE: u32 = 268; diff --git a/src/control.rs b/src/control.rs index 934bade2..44024649 100644 --- a/src/control.rs +++ b/src/control.rs @@ -226,6 +226,43 @@ impl<'a> Control<'a> { .await } + pub async fn start_ap_open(&mut self, ssid: &str, channel: u8) { + // Temporarily set wifi down + self.ioctl(IoctlType::Set, IOCTL_CMD_DOWN, 0, &mut []).await; + + // Turn off APSTA mode + self.set_iovar_u32("apsta", 0).await; + + // Set wifi up again + self.ioctl(IoctlType::Set, IOCTL_CMD_UP, 0, &mut []).await; + + // Turn on AP mode + self.ioctl_set_u32(IOCTL_CMD_SET_AP, 0, 1).await; + + // Set SSID + let mut i = SsidInfoWithIndex { + index: 0, + ssid_info: SsidInfo { + len: ssid.as_bytes().len() as _, + ssid: [0; 32], + }, + }; + i.ssid_info.ssid[..ssid.as_bytes().len()].copy_from_slice(ssid.as_bytes()); + self.set_iovar("bsscfg:ssid", &i.to_bytes()).await; + + // Set channel number + self.ioctl_set_u32(IOCTL_CMD_SET_CHANNEL, 0, channel as u32).await; + + // Set security + self.set_iovar_u32x2("bsscfg:wsec", 0, 0).await; // wsec = open + + // Change mutlicast rate from 1 Mbps to 11 Mbps + self.set_iovar_u32("2g_mrate", 11000000 / 500000).await; + + // Start AP + self.set_iovar_u32x2("bss", 0, 1).await; // bss = BSS_UP + } + async fn set_iovar_u32x2(&mut self, name: &str, val1: u32, val2: u32) { let mut buf = [0; 8]; buf[0..4].copy_from_slice(&val1.to_le_bytes()); diff --git a/src/structs.rs b/src/structs.rs index d01d5a65..3b646e1a 100644 --- a/src/structs.rs +++ b/src/structs.rs @@ -389,6 +389,15 @@ pub struct PassphraseInfo { } impl_bytes!(PassphraseInfo); +#[derive(Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[repr(C)] +pub struct SsidInfoWithIndex { + pub index: u32, + pub ssid_info: SsidInfo, +} +impl_bytes!(SsidInfoWithIndex); + #[derive(Clone, Copy)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] #[repr(C)] From af368676ef50317c1b97b5f2134dd86503b01124 Mon Sep 17 00:00:00 2001 From: Daniel Larsen <44644910+daniel-larsen@users.noreply.github.com> Date: Sun, 30 Apr 2023 18:02:44 -0300 Subject: [PATCH 111/178] Removed defmt --- cyw43-pio/Cargo.toml | 5 +++-- cyw43-pio/src/lib.rs | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/cyw43-pio/Cargo.toml b/cyw43-pio/Cargo.toml index 4ca227d3..dbb94f61 100644 --- a/cyw43-pio/Cargo.toml +++ b/cyw43-pio/Cargo.toml @@ -7,7 +7,8 @@ edition = "2021" [dependencies] cyw43 = { path = "../" } -embassy-rp = { version = "0.1.0", features = ["defmt", "unstable-traits", "nightly", "unstable-pac", "time-driver"] } +embassy-rp = { version = "0.1.0", features = ["unstable-traits", "nightly", "unstable-pac", "time-driver"] } +# embassy-rp = { version = "0.1.0", features = ["defmt", "unstable-traits", "nightly", "unstable-pac", "time-driver"] } pio-proc = "0.2" pio = "0.2.1" -defmt = "0.3" \ No newline at end of file +# defmt = "0.3" \ No newline at end of file diff --git a/cyw43-pio/src/lib.rs b/cyw43-pio/src/lib.rs index 9c425cba..f67a55a0 100644 --- a/cyw43-pio/src/lib.rs +++ b/cyw43-pio/src/lib.rs @@ -129,7 +129,7 @@ where let write_bits = write.len() * 32 - 1; let read_bits = 31; - defmt::trace!("write={} read={}", write_bits, read_bits); + // defmt::trace!("write={} read={}", write_bits, read_bits); let mut dma = Peripheral::into_ref(&mut self.dma); pio_instr_util::set_x(&mut self.sm, write_bits as u32); @@ -151,7 +151,7 @@ where let write_bits = 31; let read_bits = read.len() * 32 + 32 - 1; - defmt::trace!("write={} read={}", write_bits, read_bits); + // defmt::trace!("write={} read={}", write_bits, read_bits); let mut dma = Peripheral::into_ref(&mut self.dma); pio_instr_util::set_y(&mut self.sm, read_bits as u32); From a186694fddd00beb4c3b45349cd79ca1959b4d17 Mon Sep 17 00:00:00 2001 From: Satoshi Tanaka Date: Mon, 1 May 2023 06:54:26 +0900 Subject: [PATCH 112/178] Implement WPA2 AP mode --- src/consts.rs | 15 +++++++++++++++ src/control.rs | 32 +++++++++++++++++++++++++++++++- 2 files changed, 46 insertions(+), 1 deletion(-) diff --git a/src/consts.rs b/src/consts.rs index ade3cb2c..1f655158 100644 --- a/src/consts.rs +++ b/src/consts.rs @@ -112,6 +112,21 @@ pub(crate) const READ: bool = false; pub(crate) const INC_ADDR: bool = true; pub(crate) const FIXED_ADDR: bool = false; +pub(crate) const AES_ENABLED: u32 = 0x0004; +pub(crate) const WPA2_SECURITY: u32 = 0x00400000; + +pub(crate) const MIN_PSK_LEN: usize = 8; +pub(crate) const MAX_PSK_LEN: usize = 64; + +// Security type (authentication and encryption types are combined using bit mask) +#[allow(non_camel_case_types)] +#[derive(Copy, Clone, PartialEq)] +#[repr(u32)] +pub(crate) enum Security { + OPEN = 0, + WPA2_AES_PSK = WPA2_SECURITY | AES_ENABLED, +} + #[allow(non_camel_case_types)] #[derive(Copy, Clone)] #[repr(u8)] diff --git a/src/control.rs b/src/control.rs index 44024649..e1ad06e6 100644 --- a/src/control.rs +++ b/src/control.rs @@ -227,6 +227,20 @@ impl<'a> Control<'a> { } pub async fn start_ap_open(&mut self, ssid: &str, channel: u8) { + self.start_ap(ssid, "", Security::OPEN, channel).await; + } + + pub async fn start_ap_wpa2(&mut self, ssid: &str, passphrase: &str, channel: u8) { + self.start_ap(ssid, passphrase, Security::WPA2_AES_PSK, channel).await; + } + + async fn start_ap(&mut self, ssid: &str, passphrase: &str, security: Security, channel: u8) { + if security != Security::OPEN + && (passphrase.as_bytes().len() < MIN_PSK_LEN || passphrase.as_bytes().len() > MAX_PSK_LEN) + { + panic!("Passphrase is too short or too long"); + } + // Temporarily set wifi down self.ioctl(IoctlType::Set, IOCTL_CMD_DOWN, 0, &mut []).await; @@ -254,7 +268,23 @@ impl<'a> Control<'a> { self.ioctl_set_u32(IOCTL_CMD_SET_CHANNEL, 0, channel as u32).await; // Set security - self.set_iovar_u32x2("bsscfg:wsec", 0, 0).await; // wsec = open + self.set_iovar_u32x2("bsscfg:wsec", 0, (security as u32) & 0xFF).await; + + if security != Security::OPEN { + self.set_iovar_u32x2("bsscfg:wpa_auth", 0, 0x0084).await; // wpa_auth = WPA2_AUTH_PSK | WPA_AUTH_PSK + + Timer::after(Duration::from_millis(100)).await; + + // Set passphrase + let mut pfi = PassphraseInfo { + len: passphrase.as_bytes().len() as _, + flags: 1, // WSEC_PASSPHRASE + passphrase: [0; 64], + }; + pfi.passphrase[..passphrase.as_bytes().len()].copy_from_slice(passphrase.as_bytes()); + self.ioctl(IoctlType::Set, IOCTL_CMD_SET_PASSPHRASE, 0, &mut pfi.to_bytes()) + .await; + } // Change mutlicast rate from 1 Mbps to 11 Mbps self.set_iovar_u32("2g_mrate", 11000000 / 500000).await; From c70a66fe815cc3926a0b8ae73066a8ed2c1583e1 Mon Sep 17 00:00:00 2001 From: Daniel Larsen <44644910+daniel-larsen@users.noreply.github.com> Date: Sun, 30 Apr 2023 18:55:19 -0300 Subject: [PATCH 113/178] Make defmt optional --- cyw43-pio/Cargo.toml | 3 +-- cyw43-pio/src/lib.rs | 6 ++++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/cyw43-pio/Cargo.toml b/cyw43-pio/Cargo.toml index dbb94f61..c5632d5f 100644 --- a/cyw43-pio/Cargo.toml +++ b/cyw43-pio/Cargo.toml @@ -8,7 +8,6 @@ edition = "2021" [dependencies] cyw43 = { path = "../" } embassy-rp = { version = "0.1.0", features = ["unstable-traits", "nightly", "unstable-pac", "time-driver"] } -# embassy-rp = { version = "0.1.0", features = ["defmt", "unstable-traits", "nightly", "unstable-pac", "time-driver"] } pio-proc = "0.2" pio = "0.2.1" -# defmt = "0.3" \ No newline at end of file +defmt = { version = "0.3", optional = true } \ No newline at end of file diff --git a/cyw43-pio/src/lib.rs b/cyw43-pio/src/lib.rs index f67a55a0..c468435f 100644 --- a/cyw43-pio/src/lib.rs +++ b/cyw43-pio/src/lib.rs @@ -129,7 +129,8 @@ where let write_bits = write.len() * 32 - 1; let read_bits = 31; - // defmt::trace!("write={} read={}", write_bits, read_bits); + #[cfg(feature = "defmt")] + defmt::trace!("write={} read={}", write_bits, read_bits); let mut dma = Peripheral::into_ref(&mut self.dma); pio_instr_util::set_x(&mut self.sm, write_bits as u32); @@ -151,7 +152,8 @@ where let write_bits = 31; let read_bits = read.len() * 32 + 32 - 1; - // defmt::trace!("write={} read={}", write_bits, read_bits); + #[cfg(feature = "defmt")] + defmt::trace!("write={} read={}", write_bits, read_bits); let mut dma = Peripheral::into_ref(&mut self.dma); pio_instr_util::set_y(&mut self.sm, read_bits as u32); From bc34f3c60f83d4f1762864e3a070a501a94fc4e2 Mon Sep 17 00:00:00 2001 From: Daniel Larsen <44644910+daniel-larsen@users.noreply.github.com> Date: Sun, 30 Apr 2023 23:19:53 -0300 Subject: [PATCH 114/178] updated example --- examples/rpi-pico-w/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/rpi-pico-w/Cargo.toml b/examples/rpi-pico-w/Cargo.toml index 970db089..8df65e18 100644 --- a/examples/rpi-pico-w/Cargo.toml +++ b/examples/rpi-pico-w/Cargo.toml @@ -6,7 +6,7 @@ edition = "2021" [dependencies] cyw43 = { path = "../../", features = ["defmt", "firmware-logs"] } -cyw43-pio = { path = "../../cyw43-pio" } +cyw43-pio = { path = "../../cyw43-pio", features = ["defmt"] } embassy-executor = { version = "0.1.0", features = ["defmt", "integrated-timers", "executor-thread", "arch-cortex-m"] } embassy-time = { version = "0.1.0", features = ["defmt", "defmt-timestamp-uptime"] } embassy-rp = { version = "0.1.0", features = ["defmt", "unstable-traits", "nightly", "unstable-pac", "time-driver"] } From b612976cc7e9fcce7b547b348adaaabe73c487d0 Mon Sep 17 00:00:00 2001 From: kbleeke Date: Fri, 28 Apr 2023 20:53:09 +0200 Subject: [PATCH 115/178] add wifi scan example --- .../src/{main.rs => bin/tcp_server.rs} | 5 +- examples/rpi-pico-w/src/bin/wifi_scan.rs | 81 +++++++++++++++++++ 2 files changed, 83 insertions(+), 3 deletions(-) rename examples/rpi-pico-w/src/{main.rs => bin/tcp_server.rs} (95%) create mode 100644 examples/rpi-pico-w/src/bin/wifi_scan.rs diff --git a/examples/rpi-pico-w/src/main.rs b/examples/rpi-pico-w/src/bin/tcp_server.rs similarity index 95% rename from examples/rpi-pico-w/src/main.rs rename to examples/rpi-pico-w/src/bin/tcp_server.rs index 944beaac..036f7930 100644 --- a/examples/rpi-pico-w/src/main.rs +++ b/examples/rpi-pico-w/src/bin/tcp_server.rs @@ -48,9 +48,8 @@ async fn main(spawner: Spawner) { let p = embassy_rp::init(Default::default()); - // Include the WiFi firmware and Country Locale Matrix (CLM) blobs. - let fw = include_bytes!("../../../firmware/43439A0.bin"); - let clm = include_bytes!("../../../firmware/43439A0_clm.bin"); + let fw = include_bytes!("../../../../firmware/43439A0.bin"); + let clm = include_bytes!("../../../../firmware/43439A0_clm.bin"); // To make flashing faster for development, you may want to flash the firmwares independently // at hardcoded addresses, instead of baking them into the program with `include_bytes!`: diff --git a/examples/rpi-pico-w/src/bin/wifi_scan.rs b/examples/rpi-pico-w/src/bin/wifi_scan.rs new file mode 100644 index 00000000..da8fadfd --- /dev/null +++ b/examples/rpi-pico-w/src/bin/wifi_scan.rs @@ -0,0 +1,81 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] +#![feature(async_fn_in_trait)] +#![allow(incomplete_features)] + +use core::str; + +use cyw43_pio::PioSpi; +use defmt::*; +use embassy_executor::Spawner; +use embassy_net::Stack; +use embassy_rp::gpio::{Level, Output}; +use embassy_rp::peripherals::{DMA_CH0, PIN_23, PIN_25}; +use embassy_rp::pio::{Pio0, PioPeripheral, PioStateMachineInstance, Sm0}; +use static_cell::StaticCell; +use {defmt_rtt as _, panic_probe as _}; + +macro_rules! singleton { + ($val:expr) => {{ + type T = impl Sized; + static STATIC_CELL: StaticCell = StaticCell::new(); + STATIC_CELL.init_with(move || $val) + }}; +} + +#[embassy_executor::task] +async fn wifi_task( + runner: cyw43::Runner< + 'static, + Output<'static, PIN_23>, + PioSpi, DMA_CH0>, + >, +) -> ! { + 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_rp::init(Default::default()); + + let fw = include_bytes!("../../../../firmware/43439A0.bin"); + let clm = include_bytes!("../../../../firmware/43439A0_clm.bin"); + + // To make flashing faster for development, you may want to flash the firmwares independently + // at hardcoded addresses, instead of baking them into the program with `include_bytes!`: + // probe-rs-cli download 43439A0.bin --format bin --chip RP2040 --base-address 0x10100000 + // probe-rs-cli download 43439A0_clm.bin --format bin --chip RP2040 --base-address 0x10140000 + //let fw = unsafe { core::slice::from_raw_parts(0x10100000 as *const u8, 224190) }; + //let clm = unsafe { core::slice::from_raw_parts(0x10140000 as *const u8, 4752) }; + + let pwr = Output::new(p.PIN_23, Level::Low); + let cs = Output::new(p.PIN_25, Level::High); + + let (_, sm, _, _, _) = p.PIO0.split(); + let dma = p.DMA_CH0; + let spi = PioSpi::new(sm, cs, p.PIN_24, p.PIN_29, dma); + + let state = singleton!(cyw43::State::new()); + let (_net_device, mut control, runner) = cyw43::new(state, pwr, spi, fw).await; + unwrap!(spawner.spawn(wifi_task(runner))); + + control.init(clm).await; + control + .set_power_management(cyw43::PowerManagementMode::PowerSave) + .await; + + let mut scanner = control.scan().await; + while let Some(bss) = scanner.next().await { + if let Ok(ssid_str) = str::from_utf8(&bss.ssid) { + info!("scanned {} == {:x}", ssid_str, bss.bssid); + } + } +} From 534cf7c618b0e93881b9757b5608a7ad67606fce Mon Sep 17 00:00:00 2001 From: Satoshi Tanaka Date: Tue, 2 May 2023 01:30:08 +0900 Subject: [PATCH 116/178] Add AP mode example --- examples/rpi-pico-w/src/bin/tcp_server_ap.rs | 144 +++++++++++++++++++ 1 file changed, 144 insertions(+) create mode 100644 examples/rpi-pico-w/src/bin/tcp_server_ap.rs diff --git a/examples/rpi-pico-w/src/bin/tcp_server_ap.rs b/examples/rpi-pico-w/src/bin/tcp_server_ap.rs new file mode 100644 index 00000000..e4341262 --- /dev/null +++ b/examples/rpi-pico-w/src/bin/tcp_server_ap.rs @@ -0,0 +1,144 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] +#![feature(async_fn_in_trait)] +#![allow(incomplete_features)] + +use core::str::from_utf8; + +use cyw43_pio::PioSpi; +use defmt::*; +use embassy_executor::Spawner; +use embassy_net::tcp::TcpSocket; +use embassy_net::{Config, Stack, StackResources}; +use embassy_rp::gpio::{Level, Output}; +use embassy_rp::peripherals::{DMA_CH0, PIN_23, PIN_25}; +use embassy_rp::pio::{Pio0, PioPeripheral, PioStateMachineInstance, Sm0}; +use embedded_io::asynch::Write; +use static_cell::StaticCell; +use {defmt_rtt as _, panic_probe as _}; + +macro_rules! singleton { + ($val:expr) => {{ + type T = impl Sized; + static STATIC_CELL: StaticCell = StaticCell::new(); + STATIC_CELL.init_with(move || $val) + }}; +} + +#[embassy_executor::task] +async fn wifi_task( + runner: cyw43::Runner< + 'static, + Output<'static, PIN_23>, + PioSpi, DMA_CH0>, + >, +) -> ! { + 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_rp::init(Default::default()); + + let fw = include_bytes!("../../../../firmware/43439A0.bin"); + let clm = include_bytes!("../../../../firmware/43439A0_clm.bin"); + + // To make flashing faster for development, you may want to flash the firmwares independently + // at hardcoded addresses, instead of baking them into the program with `include_bytes!`: + // probe-rs-cli download 43439A0.bin --format bin --chip RP2040 --base-address 0x10100000 + // probe-rs-cli download 43439A0_clm.bin --format bin --chip RP2040 --base-address 0x10140000 + //let fw = unsafe { core::slice::from_raw_parts(0x10100000 as *const u8, 224190) }; + //let clm = unsafe { core::slice::from_raw_parts(0x10140000 as *const u8, 4752) }; + + let pwr = Output::new(p.PIN_23, Level::Low); + let cs = Output::new(p.PIN_25, Level::High); + + let (_, sm, _, _, _) = p.PIO0.split(); + let dma = p.DMA_CH0; + let spi = PioSpi::new(sm, cs, p.PIN_24, p.PIN_29, dma); + + let state = singleton!(cyw43::State::new()); + let (net_device, mut control, runner) = cyw43::new(state, pwr, spi, fw).await; + unwrap!(spawner.spawn(wifi_task(runner))); + + control.init(clm).await; + control + .set_power_management(cyw43::PowerManagementMode::PowerSave) + .await; + + // Use a link-local address for communication without DHCP server + let config = Config::Static(embassy_net::StaticConfig { + address: embassy_net::Ipv4Cidr::new(embassy_net::Ipv4Address::new(169, 254, 1, 1), 16), + dns_servers: heapless::Vec::new(), + gateway: None, + }); + + // Generate random seed + let seed = 0x0123_4567_89ab_cdef; // chosen by fair dice roll. guarenteed to be random. + + // Init network stack + let stack = &*singleton!(Stack::new( + net_device, + config, + singleton!(StackResources::<2>::new()), + seed + )); + + unwrap!(spawner.spawn(net_task(stack))); + + //control.start_ap_open("cyw43", 5).await; + control.start_ap_wpa2("cyw43", "password", 5).await; + + // 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_net::SmolDuration::from_secs(10))); + + control.gpio_set(0, false).await; + 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()); + control.gpio_set(0, true).await; + + 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 {}", from_utf8(&buf[..n]).unwrap()); + + match socket.write_all(&buf[..n]).await { + Ok(()) => {} + Err(e) => { + warn!("write error: {:?}", e); + break; + } + }; + } + } +} From 6ee45f5ec01208bdcb38f23bf46dcdac141ff6e7 Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Mon, 1 May 2023 18:47:09 +0200 Subject: [PATCH 117/178] Update README.md --- README.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index d0920aae..fe8d5d93 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,8 @@ WIP driver for the CYW43439 wifi chip, used in the Raspberry Pi Pico W. Implemen Working: - Station mode (joining an AP). +- AP mode (creating an AP) +- Scanning - Sending and receiving Ethernet frames. - Using the default MAC address. - [`embassy-net`](https://embassy.dev) integration. @@ -16,10 +18,7 @@ Working: TODO: -- AP mode (creating an AP) -- Scanning - Setting a custom MAC address. -- Investigate why can [this](https://github.com/raspberrypi/pico-sdk/tree/master/src/rp2_common/pico_cyw43_driver) use higher PIO speed. - Bus sleep (unclear what the benefit is. Is it needed for IRQs? or is it just power consumption optimization?) ## Running the example From 8dbe397f993cb3a9c330c3d6d5a90a8695d87c67 Mon Sep 17 00:00:00 2001 From: Kai Bleeke <39027073+kbleeke@users.noreply.github.com> Date: Wed, 3 May 2023 20:15:43 +0200 Subject: [PATCH 118/178] cleanup ioctl response logging --- src/ioctl.rs | 2 +- src/runner.rs | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/ioctl.rs b/src/ioctl.rs index 66c6a10e..61524c27 100644 --- a/src/ioctl.rs +++ b/src/ioctl.rs @@ -110,7 +110,7 @@ impl IoctlState { pub fn ioctl_done(&self, response: &[u8]) { if let IoctlStateInner::Sent { buf } = self.state.get() { - info!("IOCTL Response: {:02x}", Bytes(response)); + trace!("IOCTL Response: {:02x}", Bytes(response)); // TODO fix this (unsafe { &mut *buf }[..response.len()]).copy_from_slice(response); diff --git a/src/runner.rs b/src/runner.rs index 9b99e174..56b9a609 100644 --- a/src/runner.rs +++ b/src/runner.rs @@ -351,8 +351,6 @@ where panic!("IOCTL error {}", cdc_header.status as i32); } - info!("IOCTL Response: {:02x}", Bytes(response)); - self.ioctl_state.ioctl_done(response); } } From 0d8d8d3320ad44eda53d4ac793fb7c9fed03b63a Mon Sep 17 00:00:00 2001 From: kbleeke Date: Wed, 3 May 2023 21:49:35 +0200 Subject: [PATCH 119/178] simple error handling for join instead of looping internally --- examples/rpi-pico-w/src/bin/tcp_server.rs | 11 ++++- src/control.rs | 50 +++++++++++++++-------- src/lib.rs | 2 +- 3 files changed, 42 insertions(+), 21 deletions(-) diff --git a/examples/rpi-pico-w/src/bin/tcp_server.rs b/examples/rpi-pico-w/src/bin/tcp_server.rs index 036f7930..9581602a 100644 --- a/examples/rpi-pico-w/src/bin/tcp_server.rs +++ b/examples/rpi-pico-w/src/bin/tcp_server.rs @@ -94,8 +94,15 @@ async fn main(spawner: Spawner) { unwrap!(spawner.spawn(net_task(stack))); - //control.join_open(env!("WIFI_NETWORK")).await; - control.join_wpa2(env!("WIFI_NETWORK"), env!("WIFI_PASSWORD")).await; + loop { + //control.join_open(env!("WIFI_NETWORK")).await; + match control.join_wpa2(env!("WIFI_NETWORK"), env!("WIFI_PASSWORD")).await { + Ok(_) => break, + Err(err) => { + info!("join failed with status={}", err.status); + } + } + } // And now we can use it! diff --git a/src/control.rs b/src/control.rs index e1ad06e6..3d7d4dd3 100644 --- a/src/control.rs +++ b/src/control.rs @@ -12,6 +12,11 @@ use crate::ioctl::{IoctlState, IoctlType}; use crate::structs::*; use crate::{countries, events, PowerManagementMode}; +#[derive(Debug)] +pub struct Error { + pub status: u32, +} + pub struct Control<'a> { state_ch: ch::StateRunner<'a>, events: &'a Events, @@ -145,7 +150,7 @@ impl<'a> Control<'a> { self.ioctl_set_u32(86, 0, mode_num).await; } - pub async fn join_open(&mut self, ssid: &str) { + pub async fn join_open(&mut self, ssid: &str) -> Result<(), Error> { self.set_iovar_u32("ampdu_ba_wsize", 8).await; self.ioctl_set_u32(134, 0, 0).await; // wsec = open @@ -159,10 +164,10 @@ impl<'a> Control<'a> { }; i.ssid[..ssid.len()].copy_from_slice(ssid.as_bytes()); - self.wait_for_join(i).await; + self.wait_for_join(i).await } - pub async fn join_wpa2(&mut self, ssid: &str, passphrase: &str) { + pub async fn join_wpa2(&mut self, ssid: &str, passphrase: &str) -> Result<(), Error> { self.set_iovar_u32("ampdu_ba_wsize", 8).await; self.ioctl_set_u32(134, 0, 4).await; // wsec = wpa2 @@ -191,33 +196,42 @@ impl<'a> Control<'a> { }; i.ssid[..ssid.len()].copy_from_slice(ssid.as_bytes()); - self.wait_for_join(i).await; + self.wait_for_join(i).await } - async fn wait_for_join(&mut self, i: SsidInfo) { - self.events.mask.enable(&[Event::JOIN, Event::AUTH]); + async fn wait_for_join(&mut self, i: SsidInfo) -> Result<(), Error> { + self.events.mask.enable(&[Event::SET_SSID, Event::AUTH]); let mut subscriber = self.events.queue.subscriber().unwrap(); // the actual join operation starts here // we make sure to enable events before so we don't miss any + + // set_ssid self.ioctl(IoctlType::Set, IOCTL_CMD_SET_SSID, 0, &mut i.to_bytes()) .await; - // set_ssid - loop { + // to complete the join, we wait for a SET_SSID event + // we also save the AUTH status for the user, it may be interesting + let mut auth_status = 0; + let status = loop { let msg = subscriber.next_message_pure().await; if msg.header.event_type == Event::AUTH && msg.header.status != EStatus::SUCCESS { - // retry - warn!("JOIN failed with status={}", msg.header.status); - self.ioctl(IoctlType::Set, IOCTL_CMD_SET_SSID, 0, &mut i.to_bytes()) - .await; - } else if msg.header.event_type == Event::JOIN && msg.header.status == EStatus::SUCCESS { - // successful join - break; + auth_status = msg.header.status; + } else if msg.header.event_type == Event::SET_SSID { + // join operation ends with SET_SSID event + break msg.header.status; } - } + }; + self.events.mask.disable_all(); - self.state_ch.set_link_state(LinkState::Up); - info!("JOINED"); + if status == EStatus::SUCCESS { + // successful join + self.state_ch.set_link_state(LinkState::Up); + info!("JOINED"); + Ok(()) + } else { + warn!("JOIN failed with status={} auth={}", status, auth_status); + Err(Error { status }) + } } pub async fn gpio_set(&mut self, gpio_n: u8, gpio_en: bool) { diff --git a/src/lib.rs b/src/lib.rs index d437a882..4a9ada90 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -27,7 +27,7 @@ use ioctl::IoctlState; use crate::bus::Bus; pub use crate::bus::SpiBusCyw43; -pub use crate::control::Control; +pub use crate::control::{Control, Error as ControlError}; pub use crate::runner::Runner; pub use crate::structs::BssInfo; From 008b1fd30c945fdfb36d95dcfc38ffa9cbcf2cf1 Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Mon, 8 May 2023 20:26:17 +0200 Subject: [PATCH 120/178] update defmt to 0.3.4, now that probe-run is fixed. --- examples/rpi-pico-w/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/rpi-pico-w/Cargo.toml b/examples/rpi-pico-w/Cargo.toml index 8df65e18..5b46726d 100644 --- a/examples/rpi-pico-w/Cargo.toml +++ b/examples/rpi-pico-w/Cargo.toml @@ -14,7 +14,7 @@ embassy-net = { version = "0.1.0", features = ["defmt", "tcp", "dhcpv4", "medium atomic-polyfill = "0.1.5" static_cell = "1.0" -defmt = "=0.3.2" +defmt = "0.3.4" defmt-rtt = "0.3" panic-probe = { version = "0.3", features = ["print-defmt"] } From d3d424dad348c78222a6d962e2d51b56b485807d Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Mon, 8 May 2023 20:27:21 +0200 Subject: [PATCH 121/178] remove comment. --- cyw43-pio/Cargo.toml | 2 -- 1 file changed, 2 deletions(-) diff --git a/cyw43-pio/Cargo.toml b/cyw43-pio/Cargo.toml index c5632d5f..8272903c 100644 --- a/cyw43-pio/Cargo.toml +++ b/cyw43-pio/Cargo.toml @@ -3,8 +3,6 @@ name = "cyw43-pio" version = "0.1.0" edition = "2021" -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - [dependencies] cyw43 = { path = "../" } embassy-rp = { version = "0.1.0", features = ["unstable-traits", "nightly", "unstable-pac", "time-driver"] } From a7dee5b65c602637f8209d46d4611ed846a17459 Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Mon, 8 May 2023 20:27:44 +0200 Subject: [PATCH 122/178] Change all logging level to debug. --- src/control.rs | 14 +++++++------- src/runner.rs | 22 +++++++++++----------- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/control.rs b/src/control.rs index 3d7d4dd3..6919d569 100644 --- a/src/control.rs +++ b/src/control.rs @@ -35,7 +35,7 @@ impl<'a> Control<'a> { pub async fn init(&mut self, clm: &[u8]) { const CHUNK_SIZE: usize = 1024; - info!("Downloading CLM..."); + debug!("Downloading CLM..."); let mut offs = 0; for chunk in clm.chunks(CHUNK_SIZE) { @@ -65,7 +65,7 @@ impl<'a> Control<'a> { // check clmload ok assert_eq!(self.get_iovar_u32("clmload_status").await, 0); - info!("Configuring misc stuff..."); + debug!("Configuring misc stuff..."); // Disable tx gloming which transfers multiple packets in one request. // 'glom' is short for "conglomerate" which means "gather together into @@ -76,7 +76,7 @@ impl<'a> Control<'a> { // read MAC addr. let mut mac_addr = [0; 6]; assert_eq!(self.get_iovar("cur_etheraddr", &mut mac_addr).await, 6); - info!("mac addr: {:02x}", Bytes(&mac_addr)); + debug!("mac addr: {:02x}", Bytes(&mac_addr)); let country = countries::WORLD_WIDE_XX; let country_info = CountryInfo { @@ -135,7 +135,7 @@ impl<'a> Control<'a> { self.state_ch.set_ethernet_address(mac_addr); - info!("INIT DONE"); + debug!("INIT DONE"); } pub async fn set_power_management(&mut self, mode: PowerManagementMode) { @@ -226,7 +226,7 @@ impl<'a> Control<'a> { if status == EStatus::SUCCESS { // successful join self.state_ch.set_link_state(LinkState::Up); - info!("JOINED"); + debug!("JOINED"); Ok(()) } else { warn!("JOIN failed with status={} auth={}", status, auth_status); @@ -330,7 +330,7 @@ impl<'a> Control<'a> { } async fn set_iovar_v(&mut self, name: &str, val: &[u8]) { - info!("set {} = {:02x}", name, Bytes(val)); + debug!("set {} = {:02x}", name, Bytes(val)); let mut buf = [0; BUFSIZE]; buf[..name.len()].copy_from_slice(name.as_bytes()); @@ -344,7 +344,7 @@ impl<'a> Control<'a> { // TODO this is not really working, it always returns all zeros. async fn get_iovar(&mut self, name: &str, res: &mut [u8]) -> usize { - info!("get {}", name); + debug!("get {}", name); let mut buf = [0; 64]; buf[..name.len()].copy_from_slice(name.as_bytes()); diff --git a/src/runner.rs b/src/runner.rs index 56b9a609..98f8aff7 100644 --- a/src/runner.rs +++ b/src/runner.rs @@ -80,12 +80,12 @@ where self.bus .write8(FUNC_BACKPLANE, REG_BACKPLANE_CHIP_CLOCK_CSR, BACKPLANE_ALP_AVAIL_REQ) .await; - info!("waiting for clock..."); + debug!("waiting for clock..."); while self.bus.read8(FUNC_BACKPLANE, REG_BACKPLANE_CHIP_CLOCK_CSR).await & BACKPLANE_ALP_AVAIL == 0 {} - info!("clock ok"); + debug!("clock ok"); let chip_id = self.bus.bp_read16(0x1800_0000).await; - info!("chip ID: {}", chip_id); + debug!("chip ID: {}", chip_id); // Upload firmware. self.core_disable(Core::WLAN).await; @@ -95,10 +95,10 @@ where let ram_addr = CHIP.atcm_ram_base_address; - info!("loading fw"); + debug!("loading fw"); self.bus.bp_write(ram_addr, firmware).await; - info!("loading nvram"); + debug!("loading nvram"); // Round up to 4 bytes. let nvram_len = (NVRAM.len() + 3) / 4 * 4; self.bus @@ -112,7 +112,7 @@ where .await; // Start core! - info!("starting up core..."); + debug!("starting up core..."); self.core_reset(Core::WLAN).await; assert!(self.core_is_up(Core::WLAN).await); @@ -132,7 +132,7 @@ where .await; // wait for wifi startup - info!("waiting for wifi init..."); + debug!("waiting for wifi init..."); while self.bus.read32(FUNC_BUS, REG_BUS_STATUS).await & STATUS_F2_RX_READY == 0 {} // Some random configs related to sleep. @@ -158,14 +158,14 @@ where // start HT clock //self.bus.write8(FUNC_BACKPLANE, REG_BACKPLANE_CHIP_CLOCK_CSR, 0x10).await; - //info!("waiting for HT clock..."); + //debug!("waiting for HT clock..."); //while self.bus.read8(FUNC_BACKPLANE, REG_BACKPLANE_CHIP_CLOCK_CSR).await & 0x80 == 0 {} - //info!("clock ok"); + //debug!("clock ok"); #[cfg(feature = "firmware-logs")] self.log_init().await; - info!("init done "); + debug!("wifi init done"); } #[cfg(feature = "firmware-logs")] @@ -174,7 +174,7 @@ where let addr = CHIP.atcm_ram_base_address + CHIP.chip_ram_size - 4 - CHIP.socram_srmem_size; let shared_addr = self.bus.bp_read32(addr).await; - info!("shared_addr {:08x}", shared_addr); + debug!("shared_addr {:08x}", shared_addr); let mut shared = [0; SharedMemData::SIZE]; self.bus.bp_read(shared_addr, &mut shared).await; From 881e9d07d2e1107b27952c8bfe1d5afeef172165 Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Mon, 8 May 2023 21:45:54 +0200 Subject: [PATCH 123/178] Fix missing padding in tx. Makes max-sized packets not get dropped --- src/runner.rs | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/src/runner.rs b/src/runner.rs index 98f8aff7..1d8ec435 100644 --- a/src/runner.rs +++ b/src/runner.rs @@ -249,7 +249,16 @@ where let mut buf = [0; 512]; let buf8 = slice8_mut(&mut buf); - let total_len = SdpcmHeader::SIZE + BcdHeader::SIZE + packet.len(); + // There MUST be 2 bytes of padding between the SDPCM and BCD headers. + // And ONLY for data packets! + // No idea why, but the firmware will append two zero bytes to the tx'd packets + // otherwise. If the packet is exactly 1514 bytes (the max MTU), this makes it + // be oversized and get dropped. + // WHD adds it here https://github.com/Infineon/wifi-host-driver/blob/c04fcbb6b0d049304f376cf483fd7b1b570c8cd5/WiFi_Host_Driver/src/include/whd_sdpcm.h#L90 + // and adds it to the header size her https://github.com/Infineon/wifi-host-driver/blob/c04fcbb6b0d049304f376cf483fd7b1b570c8cd5/WiFi_Host_Driver/src/whd_sdpcm.c#L597 + // ¯\_(ツ)_/¯ + const PADDING_SIZE: usize = 2; + let total_len = SdpcmHeader::SIZE + PADDING_SIZE + BcdHeader::SIZE + packet.len(); let seq = self.sdpcm_seq; self.sdpcm_seq = self.sdpcm_seq.wrapping_add(1); @@ -260,7 +269,7 @@ where sequence: seq, channel_and_flags: CHANNEL_TYPE_DATA, next_length: 0, - header_length: SdpcmHeader::SIZE as _, + header_length: (SdpcmHeader::SIZE + PADDING_SIZE) as _, wireless_flow_control: 0, bus_data_credit: 0, reserved: [0, 0], @@ -276,8 +285,10 @@ where trace!(" {:?}", bcd_header); buf8[0..SdpcmHeader::SIZE].copy_from_slice(&sdpcm_header.to_bytes()); - buf8[SdpcmHeader::SIZE..][..BcdHeader::SIZE].copy_from_slice(&bcd_header.to_bytes()); - buf8[SdpcmHeader::SIZE + BcdHeader::SIZE..][..packet.len()].copy_from_slice(packet); + buf8[SdpcmHeader::SIZE + PADDING_SIZE..][..BcdHeader::SIZE] + .copy_from_slice(&bcd_header.to_bytes()); + buf8[SdpcmHeader::SIZE + PADDING_SIZE + BcdHeader::SIZE..][..packet.len()] + .copy_from_slice(packet); let total_len = (total_len + 3) & !3; // round up to 4byte From 6b5d9642d583bc034ee35b88c903545e7f423b4e Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Mon, 8 May 2023 22:01:44 +0200 Subject: [PATCH 124/178] Rename BCD -> BDC. That's what Broadcom calls it. Still no idea what it means. --- src/runner.rs | 24 ++++++++++++------------ src/structs.rs | 20 ++++++++++---------- 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/src/runner.rs b/src/runner.rs index 1d8ec435..5706696b 100644 --- a/src/runner.rs +++ b/src/runner.rs @@ -249,7 +249,7 @@ where let mut buf = [0; 512]; let buf8 = slice8_mut(&mut buf); - // There MUST be 2 bytes of padding between the SDPCM and BCD headers. + // There MUST be 2 bytes of padding between the SDPCM and BDC headers. // And ONLY for data packets! // No idea why, but the firmware will append two zero bytes to the tx'd packets // otherwise. If the packet is exactly 1514 bytes (the max MTU), this makes it @@ -258,7 +258,7 @@ where // and adds it to the header size her https://github.com/Infineon/wifi-host-driver/blob/c04fcbb6b0d049304f376cf483fd7b1b570c8cd5/WiFi_Host_Driver/src/whd_sdpcm.c#L597 // ¯\_(ツ)_/¯ const PADDING_SIZE: usize = 2; - let total_len = SdpcmHeader::SIZE + PADDING_SIZE + BcdHeader::SIZE + packet.len(); + let total_len = SdpcmHeader::SIZE + PADDING_SIZE + BdcHeader::SIZE + packet.len(); let seq = self.sdpcm_seq; self.sdpcm_seq = self.sdpcm_seq.wrapping_add(1); @@ -275,19 +275,19 @@ where reserved: [0, 0], }; - let bcd_header = BcdHeader { + let bdc_header = BdcHeader { flags: BDC_VERSION << BDC_VERSION_SHIFT, priority: 0, flags2: 0, data_offset: 0, }; trace!("tx {:?}", sdpcm_header); - trace!(" {:?}", bcd_header); + trace!(" {:?}", bdc_header); buf8[0..SdpcmHeader::SIZE].copy_from_slice(&sdpcm_header.to_bytes()); - buf8[SdpcmHeader::SIZE + PADDING_SIZE..][..BcdHeader::SIZE] - .copy_from_slice(&bcd_header.to_bytes()); - buf8[SdpcmHeader::SIZE + PADDING_SIZE + BcdHeader::SIZE..][..packet.len()] + buf8[SdpcmHeader::SIZE + PADDING_SIZE..][..BdcHeader::SIZE] + .copy_from_slice(&bdc_header.to_bytes()); + buf8[SdpcmHeader::SIZE + PADDING_SIZE + BdcHeader::SIZE..][..packet.len()] .copy_from_slice(packet); let total_len = (total_len + 3) & !3; // round up to 4byte @@ -366,13 +366,13 @@ where } } CHANNEL_TYPE_EVENT => { - let Some((_, bcd_packet)) = BcdHeader::parse(payload) else { - warn!("BCD event, incomplete header"); + let Some((_, bdc_packet)) = BdcHeader::parse(payload) else { + warn!("BDC event, incomplete header"); return; }; - let Some((event_packet, evt_data)) = EventPacket::parse(bcd_packet) else { - warn!("BCD event, incomplete data"); + let Some((event_packet, evt_data)) = EventPacket::parse(bdc_packet) else { + warn!("BDC event, incomplete data"); return; }; @@ -439,7 +439,7 @@ where } } CHANNEL_TYPE_DATA => { - let Some((_, packet)) = BcdHeader::parse(payload) else { return }; + let Some((_, packet)) = BdcHeader::parse(payload) else { return }; trace!("rx pkt {:02x}", Bytes(&packet[..packet.len().min(48)])); match self.ch.try_rx_buf() { diff --git a/src/structs.rs b/src/structs.rs index 3b646e1a..5ba633c7 100644 --- a/src/structs.rs +++ b/src/structs.rs @@ -165,7 +165,7 @@ pub const BDC_VERSION_SHIFT: u8 = 4; #[derive(Debug, Clone, Copy)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] #[repr(C)] -pub struct BcdHeader { +pub struct BdcHeader { pub flags: u8, /// 802.1d Priority (low 3 bits) pub priority: u8, @@ -173,24 +173,24 @@ pub struct BcdHeader { /// Offset from end of BDC header to packet data, in 4-uint8_t words. Leaves room for optional headers. pub data_offset: u8, } -impl_bytes!(BcdHeader); +impl_bytes!(BdcHeader); -impl BcdHeader { +impl BdcHeader { pub fn parse(packet: &mut [u8]) -> Option<(&mut Self, &mut [u8])> { if packet.len() < Self::SIZE { return None; } - let (bcd_header, bcd_packet) = packet.split_at_mut(Self::SIZE); - let bcd_header = Self::from_bytes_mut(bcd_header.try_into().unwrap()); - trace!(" {:?}", bcd_header); + let (bdc_header, bdc_packet) = packet.split_at_mut(Self::SIZE); + let bdc_header = Self::from_bytes_mut(bdc_header.try_into().unwrap()); + trace!(" {:?}", bdc_header); - let packet_start = 4 * bcd_header.data_offset as usize; + let packet_start = 4 * bdc_header.data_offset as usize; - let bcd_packet = bcd_packet.get_mut(packet_start..)?; - trace!(" {:02x}", Bytes(&bcd_packet[..bcd_packet.len().min(36)])); + let bdc_packet = bdc_packet.get_mut(packet_start..)?; + trace!(" {:02x}", Bytes(&bdc_packet[..bdc_packet.len().min(36)])); - Some((bcd_header, bcd_packet)) + Some((bdc_header, bdc_packet)) } } From 72b0379125b87bcd274bdb81127dd5f0ab29d661 Mon Sep 17 00:00:00 2001 From: kalkyl Date: Tue, 9 May 2023 01:51:08 +0200 Subject: [PATCH 125/178] :rainbow: --- .gitignore | 4 + .vscode/settings.json | 5 + Cargo.toml | 26 +++++ LICENSE-APACHE | 201 ++++++++++++++++++++++++++++++++ LICENSE-MIT | 25 ++++ README.md | 7 ++ examples/.cargo/config.toml | 8 ++ examples/Cargo.toml | 70 +++++++++++ examples/README.md | 33 ++++++ examples/build.rs | 36 ++++++ examples/memory.x | 5 + examples/src/bin/multisocket.rs | 148 +++++++++++++++++++++++ examples/src/bin/tcp-client.rs | 128 ++++++++++++++++++++ examples/src/bin/tcp-server.rs | 136 +++++++++++++++++++++ examples/src/bin/udp.rs | 123 +++++++++++++++++++ rust-toolchain.toml | 8 ++ src/device.rs | 161 +++++++++++++++++++++++++ src/lib.rs | 114 ++++++++++++++++++ src/socket.rs | 114 ++++++++++++++++++ src/spi.rs | 37 ++++++ 20 files changed, 1389 insertions(+) create mode 100644 .gitignore create mode 100644 .vscode/settings.json create mode 100644 Cargo.toml create mode 100644 LICENSE-APACHE create mode 100644 LICENSE-MIT create mode 100644 README.md create mode 100644 examples/.cargo/config.toml create mode 100644 examples/Cargo.toml create mode 100644 examples/README.md create mode 100644 examples/build.rs create mode 100644 examples/memory.x create mode 100644 examples/src/bin/multisocket.rs create mode 100644 examples/src/bin/tcp-client.rs create mode 100644 examples/src/bin/tcp-server.rs create mode 100644 examples/src/bin/udp.rs create mode 100644 rust-toolchain.toml create mode 100644 src/device.rs create mode 100644 src/lib.rs create mode 100644 src/socket.rs create mode 100644 src/spi.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..a2ac3d82 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +*.iml +**/target +**/*.rs.bk +Cargo.lock diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..11fb4092 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "rust-analyzer.linkedProjects": [ + ".\\examples\\Cargo.toml" + ] +} \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 00000000..ac2257f4 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "embassy-net-w5500" +version = "0.1.0" +description = "embassy-net driver for the W5500 ethernet chip" +keywords = ["embedded", "w5500", "embassy-net", "embedded-hal-async", "ethernet", "async"] +categories = ["embedded", "hardware-support", "no-std", "network-programming", "async"] +license = "MIT OR Apache-2.0" +edition = "2021" + +[dependencies] +embedded-hal = { version = "1.0.0-alpha.10" } +embedded-hal-async = { version = "=0.2.0-alpha.1" } +embassy-net-driver-channel = { version = "0.1.0" } +embassy-time = { version = "0.1.0" } +embassy-futures = { version = "0.1.0" } +defmt = { version = "0.3", optional = true } + +[patch.crates-io] +embassy-executor = { git = "https://github.com/embassy-rs/embassy", rev = "03d6363d5af5dcaf21b52734994a466ca593d2b6" } +embassy-time = { git = "https://github.com/embassy-rs/embassy", rev = "03d6363d5af5dcaf21b52734994a466ca593d2b6" } +embassy-futures = { git = "https://github.com/embassy-rs/embassy", rev = "03d6363d5af5dcaf21b52734994a466ca593d2b6" } +embassy-sync = { git = "https://github.com/embassy-rs/embassy", rev = "03d6363d5af5dcaf21b52734994a466ca593d2b6" } +embassy-rp = { git = "https://github.com/embassy-rs/embassy", rev = "03d6363d5af5dcaf21b52734994a466ca593d2b6" } +embassy-net = { git = "https://github.com/embassy-rs/embassy", rev = "03d6363d5af5dcaf21b52734994a466ca593d2b6" } +embassy-net-driver = { git = "https://github.com/embassy-rs/embassy", rev = "03d6363d5af5dcaf21b52734994a466ca593d2b6" } +embassy-net-driver-channel = { git = "https://github.com/embassy-rs/embassy", rev = "03d6363d5af5dcaf21b52734994a466ca593d2b6" } diff --git a/LICENSE-APACHE b/LICENSE-APACHE new file mode 100644 index 00000000..ea4fa15c --- /dev/null +++ b/LICENSE-APACHE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright 2019-2022 Embassy project contributors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/LICENSE-MIT b/LICENSE-MIT new file mode 100644 index 00000000..87c05283 --- /dev/null +++ b/LICENSE-MIT @@ -0,0 +1,25 @@ +Copyright (c) 2019-2022 Embassy project contributors + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 00000000..9eaf4b70 --- /dev/null +++ b/README.md @@ -0,0 +1,7 @@ +# WIZnet W5500 `embassy-net` integration + +[`embassy-net`](https://crates.io/crates/embassy-net) integration for the WIZnet W5500 SPI ethernet chip, operating in MACRAW mode. + +Supports any SPI driver implementing [`embedded-hal-async`](https://crates.io/crates/embedded-hal-async) + +See [`examples`](https://github.com/kalkyl/embassy-net-w5500/tree/main/examples) directory for usage examples with the rp2040 [`WIZnet W5500-EVB-Pico`](https://www.wiznet.io/product-item/w5500-evb-pico/) module. \ No newline at end of file diff --git a/examples/.cargo/config.toml b/examples/.cargo/config.toml new file mode 100644 index 00000000..e6b6b4a4 --- /dev/null +++ b/examples/.cargo/config.toml @@ -0,0 +1,8 @@ +[target.'cfg(all(target_arch = "arm", target_os = "none"))'] +runner = "probe-rs-cli run --chip RP2040" + +[build] +target = "thumbv6m-none-eabi" + +[env] +DEFMT_LOG = "info" diff --git a/examples/Cargo.toml b/examples/Cargo.toml new file mode 100644 index 00000000..013a2755 --- /dev/null +++ b/examples/Cargo.toml @@ -0,0 +1,70 @@ +[package] +name = "embassy-net-w5500-examples" +version = "0.1.0" +edition = "2021" + +[dependencies] +embassy-executor = { version = "0.1.0", features = ["defmt", "integrated-timers", "executor-thread", "arch-cortex-m"] } +embassy-time = { version = "0.1.0", features = ["defmt", "defmt-timestamp-uptime"] } +embassy-rp = { version = "0.1.0", features = ["defmt", "unstable-traits", "nightly", "unstable-pac", "time-driver"] } +embassy-net = { version = "0.1.0", features = ["defmt", "tcp", "udp", "dhcpv4", "medium-ethernet", "unstable-traits", "nightly"] } +embassy-sync = { version = "0.1.0" } +embassy-futures = { version = "0.1.0" } +embassy-net-driver = { version = "0.1.0" } +embassy-net-driver-channel = { version = "0.1.0" } +atomic-polyfill = "0.1.5" +static_cell = "1.0" + +defmt = "=0.3.2" +defmt-rtt = "0.3" +panic-probe = { version = "0.3", features = ["print-defmt"] } +cortex-m = { version = "0.7.6", features = ["critical-section-single-core"] } +cortex-m-rt = "0.7.0" + +embedded-io = { version = "0.4.0", features = ["async", "defmt"] } +heapless = "0.7.15" +embedded-hal = { version = "1.0.0-alpha.10" } +embedded-hal-async = { version = "=0.2.0-alpha.1" } +rand = { version = "0.8.5", default-features = false } + +embassy-net-w5500 = { path = "../" } + +[patch.crates-io] +embassy-executor = { git = "https://github.com/embassy-rs/embassy", rev = "03d6363d5af5dcaf21b52734994a466ca593d2b6" } +embassy-time = { git = "https://github.com/embassy-rs/embassy", rev = "03d6363d5af5dcaf21b52734994a466ca593d2b6" } +embassy-futures = { git = "https://github.com/embassy-rs/embassy", rev = "03d6363d5af5dcaf21b52734994a466ca593d2b6" } +embassy-sync = { git = "https://github.com/embassy-rs/embassy", rev = "03d6363d5af5dcaf21b52734994a466ca593d2b6" } +embassy-rp = { git = "https://github.com/embassy-rs/embassy", rev = "03d6363d5af5dcaf21b52734994a466ca593d2b6" } +embassy-net = { git = "https://github.com/embassy-rs/embassy", rev = "03d6363d5af5dcaf21b52734994a466ca593d2b6" } +embassy-net-driver = { git = "https://github.com/embassy-rs/embassy", rev = "03d6363d5af5dcaf21b52734994a466ca593d2b6" } +embassy-net-driver-channel = { git = "https://github.com/embassy-rs/embassy", rev = "03d6363d5af5dcaf21b52734994a466ca593d2b6" } + +[profile.dev] +debug = 2 +debug-assertions = true +opt-level = 1 +overflow-checks = true + +[profile.release] +codegen-units = 1 +debug = 1 +debug-assertions = false +incremental = false +lto = 'fat' +opt-level = 'z' +overflow-checks = false + +# do not optimize proc-macro crates = faster builds from scratch +[profile.dev.build-override] +codegen-units = 8 +debug = false +debug-assertions = false +opt-level = 0 +overflow-checks = false + +[profile.release.build-override] +codegen-units = 8 +debug = false +debug-assertions = false +opt-level = 0 +overflow-checks = false diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 00000000..d818c4a8 --- /dev/null +++ b/examples/README.md @@ -0,0 +1,33 @@ +# Examples for the rp2040 `WIZnet W5500-EVB-Pico` board + +Examples are written for the [`WIZnet W5500-EVB-Pico`](https://www.wiznet.io/product-item/w5500-evb-pico/) board. + +## Prerequisites +```bash +cargo install probe-rs-cli +``` + +## TCP server example +```bash +cargo run --bin tcp-server --release +``` +This example implements a TCP echo server on port 1234 and using DHCP. +Send it some data, you should see it echoed back and printed in the console. + +## Multi-socket example +```bash +cargo run --bin multisocket --release +``` +This example shows how you can allow multiple simultaneous TCP connections, by having multiple sockets listening on the same port. + +## TCP client example +```bash +cargo run --bin tcp-client --release +``` +This example implements a TCP client that attempts to connect to a host on port 1234 and send it some data once per second. + +## UDP server example +```bash +cargo run --bin udp --release +``` +This example implements a UDP server listening on port 1234 and echoing back the data. diff --git a/examples/build.rs b/examples/build.rs new file mode 100644 index 00000000..3f915f93 --- /dev/null +++ b/examples/build.rs @@ -0,0 +1,36 @@ +//! This build script copies the `memory.x` file from the crate root into +//! a directory where the linker can always find it at build time. +//! For many projects this is optional, as the linker always searches the +//! project root directory -- wherever `Cargo.toml` is. However, if you +//! are using a workspace or have a more complicated build setup, this +//! build script becomes required. Additionally, by requesting that +//! Cargo re-run the build script whenever `memory.x` is changed, +//! updating `memory.x` ensures a rebuild of the application with the +//! new memory settings. + +use std::env; +use std::fs::File; +use std::io::Write; +use std::path::PathBuf; + +fn main() { + // Put `memory.x` in our output directory and ensure it's + // on the linker search path. + let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap()); + File::create(out.join("memory.x")) + .unwrap() + .write_all(include_bytes!("memory.x")) + .unwrap(); + println!("cargo:rustc-link-search={}", out.display()); + + // By default, Cargo will re-run a build script whenever + // any file in the project changes. By specifying `memory.x` + // here, we ensure the build script is only re-run when + // `memory.x` is changed. + println!("cargo:rerun-if-changed=memory.x"); + + println!("cargo:rustc-link-arg-bins=--nmagic"); + println!("cargo:rustc-link-arg-bins=-Tlink.x"); + println!("cargo:rustc-link-arg-bins=-Tlink-rp.x"); + println!("cargo:rustc-link-arg-bins=-Tdefmt.x"); +} diff --git a/examples/memory.x b/examples/memory.x new file mode 100644 index 00000000..eb8c1731 --- /dev/null +++ b/examples/memory.x @@ -0,0 +1,5 @@ +MEMORY { + BOOT2 : ORIGIN = 0x10000000, LENGTH = 0x100 + FLASH : ORIGIN = 0x10000100, LENGTH = 1024K - 0x100 + RAM : ORIGIN = 0x20000000, LENGTH = 256K +} \ No newline at end of file diff --git a/examples/src/bin/multisocket.rs b/examples/src/bin/multisocket.rs new file mode 100644 index 00000000..49bcbdbb --- /dev/null +++ b/examples/src/bin/multisocket.rs @@ -0,0 +1,148 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_futures::yield_now; +use embassy_net::{Stack, StackResources}; +use embassy_net_w5500::*; +use embassy_rp::clocks::RoscRng; +use embassy_rp::gpio::{Input, Level, Output, Pull}; +use embassy_rp::peripherals::{PIN_17, PIN_20, PIN_21, SPI0}; +use embassy_rp::spi::{Async, Config as SpiConfig, Spi}; +use embedded_hal_async::spi::ExclusiveDevice; +use embedded_io::asynch::Write; +use rand::RngCore; +use static_cell::StaticCell; +use {defmt_rtt as _, panic_probe as _}; + +macro_rules! singleton { + ($val:expr) => {{ + type T = impl Sized; + static STATIC_CELL: StaticCell = StaticCell::new(); + let (x,) = STATIC_CELL.init(($val,)); + x + }}; +} + +#[embassy_executor::task] +async fn ethernet_task( + runner: Runner< + 'static, + ExclusiveDevice, Output<'static, PIN_17>>, + Input<'static, PIN_21>, + Output<'static, PIN_20>, + >, +) -> ! { + runner.run().await +} + +#[embassy_executor::task] +async fn net_task(stack: &'static Stack>) -> ! { + stack.run().await +} + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + let mut rng = RoscRng; + + let mut spi_cfg = SpiConfig::default(); + spi_cfg.frequency = 50_000_000; + let (miso, mosi, clk) = (p.PIN_16, p.PIN_19, p.PIN_18); + let spi = Spi::new(p.SPI0, clk, mosi, miso, p.DMA_CH0, p.DMA_CH1, spi_cfg); + let cs = Output::new(p.PIN_17, Level::High); + let w5500_int = Input::new(p.PIN_21, Pull::Up); + let w5500_reset = Output::new(p.PIN_20, Level::High); + + let mac_addr = [0x02, 0x00, 0x00, 0x00, 0x00, 0x00]; + let state = singleton!(State::<8, 8>::new()); + let (device, runner) = embassy_net_w5500::new( + mac_addr, + state, + ExclusiveDevice::new(spi, cs), + w5500_int, + w5500_reset, + ) + .await; + unwrap!(spawner.spawn(ethernet_task(runner))); + + // Generate random seed + let seed = rng.next_u64(); + + // Init network stack + let stack = &*singleton!(Stack::new( + device, + embassy_net::Config::Dhcp(Default::default()), + singleton!(StackResources::<3>::new()), + seed + )); + + // Launch network task + unwrap!(spawner.spawn(net_task(&stack))); + + info!("Waiting for DHCP..."); + let cfg = wait_for_config(stack).await; + let local_addr = cfg.address.address(); + info!("IP address: {:?}", local_addr); + + // Create two sockets listening to the same port, to handle simultaneous connections + unwrap!(spawner.spawn(listen_task(&stack, 0, 1234))); + unwrap!(spawner.spawn(listen_task(&stack, 1, 1234))); +} + +#[embassy_executor::task(pool_size = 2)] +async fn listen_task(stack: &'static Stack>, id: u8, port: u16) { + let mut rx_buffer = [0; 4096]; + let mut tx_buffer = [0; 4096]; + let mut buf = [0; 4096]; + loop { + let mut socket = embassy_net::tcp::TcpSocket::new(stack, &mut rx_buffer, &mut tx_buffer); + socket.set_timeout(Some(embassy_net::SmolDuration::from_secs(10))); + + info!("SOCKET {}: Listening on TCP:{}...", id, port); + if let Err(e) = socket.accept(port).await { + warn!("accept error: {:?}", e); + continue; + } + info!( + "SOCKET {}: Received connection from {:?}", + id, + socket.remote_endpoint() + ); + + loop { + let n = match socket.read(&mut buf).await { + Ok(0) => { + warn!("read EOF"); + break; + } + Ok(n) => n, + Err(e) => { + warn!("SOCKET {}: {:?}", id, e); + break; + } + }; + info!( + "SOCKET {}: rxd {}", + id, + core::str::from_utf8(&buf[..n]).unwrap() + ); + + if let Err(e) = socket.write_all(&buf[..n]).await { + warn!("write error: {:?}", e); + break; + } + } + } +} + +async fn wait_for_config(stack: &'static Stack>) -> embassy_net::StaticConfig { + loop { + if let Some(config) = stack.config() { + return config.clone(); + } + yield_now().await; + } +} diff --git a/examples/src/bin/tcp-client.rs b/examples/src/bin/tcp-client.rs new file mode 100644 index 00000000..32dfb6a6 --- /dev/null +++ b/examples/src/bin/tcp-client.rs @@ -0,0 +1,128 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use core::str::FromStr; +use defmt::*; +use embassy_executor::Spawner; +use embassy_futures::yield_now; +use embassy_net::{Stack, StackResources}; +use embassy_net_w5500::*; +use embassy_rp::clocks::RoscRng; +use embassy_rp::gpio::{Input, Level, Output, Pull}; +use embassy_rp::peripherals::{PIN_17, PIN_20, PIN_21, SPI0}; +use embassy_rp::spi::{Async, Config as SpiConfig, Spi}; +use embassy_time::{Duration, Timer}; +use embedded_hal_async::spi::ExclusiveDevice; +use embedded_io::asynch::Write; +use rand::RngCore; +use static_cell::StaticCell; +use {defmt_rtt as _, panic_probe as _}; + +macro_rules! singleton { + ($val:expr) => {{ + type T = impl Sized; + static STATIC_CELL: StaticCell = StaticCell::new(); + let (x,) = STATIC_CELL.init(($val,)); + x + }}; +} + +#[embassy_executor::task] +async fn ethernet_task( + runner: Runner< + 'static, + ExclusiveDevice, Output<'static, PIN_17>>, + Input<'static, PIN_21>, + Output<'static, PIN_20>, + >, +) -> ! { + runner.run().await +} + +#[embassy_executor::task] +async fn net_task(stack: &'static Stack>) -> ! { + stack.run().await +} + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + let mut rng = RoscRng; + let mut led = Output::new(p.PIN_25, Level::Low); + + let mut spi_cfg = SpiConfig::default(); + spi_cfg.frequency = 50_000_000; + let (miso, mosi, clk) = (p.PIN_16, p.PIN_19, p.PIN_18); + let spi = Spi::new(p.SPI0, clk, mosi, miso, p.DMA_CH0, p.DMA_CH1, spi_cfg); + let cs = Output::new(p.PIN_17, Level::High); + let w5500_int = Input::new(p.PIN_21, Pull::Up); + let w5500_reset = Output::new(p.PIN_20, Level::High); + + let mac_addr = [0x02, 0x00, 0x00, 0x00, 0x00, 0x00]; + let state = singleton!(State::<8, 8>::new()); + let (device, runner) = embassy_net_w5500::new( + mac_addr, + state, + ExclusiveDevice::new(spi, cs), + w5500_int, + w5500_reset, + ) + .await; + unwrap!(spawner.spawn(ethernet_task(runner))); + + // Generate random seed + let seed = rng.next_u64(); + + // Init network stack + let stack = &*singleton!(Stack::new( + device, + embassy_net::Config::Dhcp(Default::default()), + singleton!(StackResources::<2>::new()), + seed + )); + + // Launch network task + unwrap!(spawner.spawn(net_task(&stack))); + + info!("Waiting for DHCP..."); + let cfg = wait_for_config(stack).await; + let local_addr = cfg.address.address(); + info!("IP address: {:?}", local_addr); + + let mut rx_buffer = [0; 4096]; + let mut tx_buffer = [0; 4096]; + loop { + let mut socket = embassy_net::tcp::TcpSocket::new(stack, &mut rx_buffer, &mut tx_buffer); + socket.set_timeout(Some(embassy_net::SmolDuration::from_secs(10))); + + led.set_low(); + info!("Connecting..."); + let host_addr = embassy_net::Ipv4Address::from_str("192.168.1.110").unwrap(); + if let Err(e) = socket.connect((host_addr, 1234)).await { + warn!("connect error: {:?}", e); + continue; + } + info!("Connected to {:?}", socket.remote_endpoint()); + led.set_high(); + + let msg = b"Hello world!\n"; + loop { + if let Err(e) = socket.write_all(msg).await { + warn!("write error: {:?}", e); + break; + } + info!("txd: {}", core::str::from_utf8(msg).unwrap()); + Timer::after(Duration::from_secs(1)).await; + } + } +} + +async fn wait_for_config(stack: &'static Stack>) -> embassy_net::StaticConfig { + loop { + if let Some(config) = stack.config() { + return config.clone(); + } + yield_now().await; + } +} diff --git a/examples/src/bin/tcp-server.rs b/examples/src/bin/tcp-server.rs new file mode 100644 index 00000000..04b22014 --- /dev/null +++ b/examples/src/bin/tcp-server.rs @@ -0,0 +1,136 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_futures::yield_now; +use embassy_net::{Stack, StackResources}; +use embassy_net_w5500::*; +use embassy_rp::clocks::RoscRng; +use embassy_rp::gpio::{Input, Level, Output, Pull}; +use embassy_rp::peripherals::{PIN_17, PIN_20, PIN_21, SPI0}; +use embassy_rp::spi::{Async, Config as SpiConfig, Spi}; +use embedded_hal_async::spi::ExclusiveDevice; +use embedded_io::asynch::Write; +use rand::RngCore; +use static_cell::StaticCell; +use {defmt_rtt as _, panic_probe as _}; + +macro_rules! singleton { + ($val:expr) => {{ + type T = impl Sized; + static STATIC_CELL: StaticCell = StaticCell::new(); + let (x,) = STATIC_CELL.init(($val,)); + x + }}; +} + +#[embassy_executor::task] +async fn ethernet_task( + runner: Runner< + 'static, + ExclusiveDevice, Output<'static, PIN_17>>, + Input<'static, PIN_21>, + Output<'static, PIN_20>, + >, +) -> ! { + runner.run().await +} + +#[embassy_executor::task] +async fn net_task(stack: &'static Stack>) -> ! { + stack.run().await +} + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + let mut rng = RoscRng; + let mut led = Output::new(p.PIN_25, Level::Low); + + let mut spi_cfg = SpiConfig::default(); + spi_cfg.frequency = 50_000_000; + let (miso, mosi, clk) = (p.PIN_16, p.PIN_19, p.PIN_18); + let spi = Spi::new(p.SPI0, clk, mosi, miso, p.DMA_CH0, p.DMA_CH1, spi_cfg); + let cs = Output::new(p.PIN_17, Level::High); + let w5500_int = Input::new(p.PIN_21, Pull::Up); + let w5500_reset = Output::new(p.PIN_20, Level::High); + + let mac_addr = [0x02, 0x00, 0x00, 0x00, 0x00, 0x00]; + let state = singleton!(State::<8, 8>::new()); + let (device, runner) = embassy_net_w5500::new( + mac_addr, + state, + ExclusiveDevice::new(spi, cs), + w5500_int, + w5500_reset, + ) + .await; + unwrap!(spawner.spawn(ethernet_task(runner))); + + // Generate random seed + let seed = rng.next_u64(); + + // Init network stack + let stack = &*singleton!(Stack::new( + device, + embassy_net::Config::Dhcp(Default::default()), + singleton!(StackResources::<2>::new()), + seed + )); + + // Launch network task + unwrap!(spawner.spawn(net_task(&stack))); + + info!("Waiting for DHCP..."); + let cfg = wait_for_config(stack).await; + let local_addr = cfg.address.address(); + info!("IP address: {:?}", local_addr); + + let mut rx_buffer = [0; 4096]; + let mut tx_buffer = [0; 4096]; + let mut buf = [0; 4096]; + loop { + let mut socket = embassy_net::tcp::TcpSocket::new(stack, &mut rx_buffer, &mut tx_buffer); + socket.set_timeout(Some(embassy_net::SmolDuration::from_secs(10))); + + led.set_low(); + 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()); + led.set_high(); + + loop { + let n = match socket.read(&mut buf).await { + Ok(0) => { + warn!("read EOF"); + break; + } + Ok(n) => n, + Err(e) => { + warn!("{:?}", e); + break; + } + }; + info!("rxd {}", core::str::from_utf8(&buf[..n]).unwrap()); + + if let Err(e) = socket.write_all(&buf[..n]).await { + warn!("write error: {:?}", e); + break; + } + } + } +} + +async fn wait_for_config(stack: &'static Stack>) -> embassy_net::StaticConfig { + loop { + if let Some(config) = stack.config() { + return config.clone(); + } + yield_now().await; + } +} diff --git a/examples/src/bin/udp.rs b/examples/src/bin/udp.rs new file mode 100644 index 00000000..4dc5e1f2 --- /dev/null +++ b/examples/src/bin/udp.rs @@ -0,0 +1,123 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_futures::yield_now; +use embassy_net::udp::UdpSocket; +use embassy_net::{PacketMetadata, Stack, StackResources}; +use embassy_net_w5500::*; +use embassy_rp::clocks::RoscRng; +use embassy_rp::gpio::{Input, Level, Output, Pull}; +use embassy_rp::peripherals::{PIN_17, PIN_20, PIN_21, SPI0}; +use embassy_rp::spi::{Async, Config as SpiConfig, Spi}; +use embedded_hal_async::spi::ExclusiveDevice; +use rand::RngCore; +use static_cell::StaticCell; +use {defmt_rtt as _, panic_probe as _}; + +macro_rules! singleton { + ($val:expr) => {{ + type T = impl Sized; + static STATIC_CELL: StaticCell = StaticCell::new(); + let (x,) = STATIC_CELL.init(($val,)); + x + }}; +} + +#[embassy_executor::task] +async fn ethernet_task( + runner: Runner< + 'static, + ExclusiveDevice, Output<'static, PIN_17>>, + Input<'static, PIN_21>, + Output<'static, PIN_20>, + >, +) -> ! { + runner.run().await +} + +#[embassy_executor::task] +async fn net_task(stack: &'static Stack>) -> ! { + stack.run().await +} + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + let mut rng = RoscRng; + + let mut spi_cfg = SpiConfig::default(); + spi_cfg.frequency = 50_000_000; + let (miso, mosi, clk) = (p.PIN_16, p.PIN_19, p.PIN_18); + let spi = Spi::new(p.SPI0, clk, mosi, miso, p.DMA_CH0, p.DMA_CH1, spi_cfg); + let cs = Output::new(p.PIN_17, Level::High); + let w5500_int = Input::new(p.PIN_21, Pull::Up); + let w5500_reset = Output::new(p.PIN_20, Level::High); + + let mac_addr = [0x02, 0x00, 0x00, 0x00, 0x00, 0x00]; + let state = singleton!(State::<8, 8>::new()); + let (device, runner) = embassy_net_w5500::new( + mac_addr, + state, + ExclusiveDevice::new(spi, cs), + w5500_int, + w5500_reset, + ) + .await; + unwrap!(spawner.spawn(ethernet_task(runner))); + + // Generate random seed + let seed = rng.next_u64(); + + // Init network stack + let stack = &*singleton!(Stack::new( + device, + embassy_net::Config::Dhcp(Default::default()), + singleton!(StackResources::<2>::new()), + seed + )); + + // Launch network task + unwrap!(spawner.spawn(net_task(&stack))); + + info!("Waiting for DHCP..."); + let cfg = wait_for_config(stack).await; + let local_addr = cfg.address.address(); + info!("IP address: {:?}", local_addr); + + // Then we can use it! + let mut rx_buffer = [0; 4096]; + let mut tx_buffer = [0; 4096]; + let mut rx_meta = [PacketMetadata::EMPTY; 16]; + let mut tx_meta = [PacketMetadata::EMPTY; 16]; + let mut buf = [0; 4096]; + loop { + let mut socket = UdpSocket::new( + stack, + &mut rx_meta, + &mut rx_buffer, + &mut tx_meta, + &mut tx_buffer, + ); + socket.bind(1234).unwrap(); + + loop { + let (n, ep) = socket.recv_from(&mut buf).await.unwrap(); + if let Ok(s) = core::str::from_utf8(&buf[..n]) { + info!("rxd from {}: {}", ep, s); + } + socket.send_to(&buf[..n], ep).await.unwrap(); + } + } +} + +async fn wait_for_config(stack: &'static Stack>) -> embassy_net::StaticConfig { + loop { + if let Some(config) = stack.config() { + return config.clone(); + } + yield_now().await; + } +} diff --git a/rust-toolchain.toml b/rust-toolchain.toml new file mode 100644 index 00000000..fb284c1e --- /dev/null +++ b/rust-toolchain.toml @@ -0,0 +1,8 @@ +# Before upgrading check that everything is available on all tier1 targets here: +# https://rust-lang.github.io/rustup-components-history +[toolchain] +channel = "nightly-2023-04-04" +components = [ "rust-src", "rustfmt" ] +targets = [ + "thumbv6m-none-eabi", +] diff --git a/src/device.rs b/src/device.rs new file mode 100644 index 00000000..3875fde0 --- /dev/null +++ b/src/device.rs @@ -0,0 +1,161 @@ +use crate::socket; +use crate::spi::SpiInterface; +use embedded_hal_async::spi::SpiDevice; + +pub const MODE: u16 = 0x00; +pub const MAC: u16 = 0x09; +pub const SOCKET_INTR: u16 = 0x18; +pub const PHY_CFG: u16 = 0x2E; + +#[repr(u8)] +pub enum RegisterBlock { + Common = 0x00, + Socket0 = 0x01, + TxBuf = 0x02, + RxBuf = 0x03, +} + +/// W5500 in MACRAW mode +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct W5500 { + bus: SpiInterface, +} + +impl W5500 { + /// Create and initialize the W5500 driver + pub async fn new(spi: SPI, mac_addr: [u8; 6]) -> Result, SPI::Error> { + let mut bus = SpiInterface(spi); + // Reset device + bus.write_frame(RegisterBlock::Common, MODE, &[0x80]) + .await?; + + // Enable interrupt pin + bus.write_frame(RegisterBlock::Common, SOCKET_INTR, &[0x01]) + .await?; + // Enable receive interrupt + bus.write_frame( + RegisterBlock::Socket0, + socket::SOCKET_INTR_MASK, + &[socket::Interrupt::Receive as u8], + ) + .await?; + + // Set MAC address + bus.write_frame(RegisterBlock::Common, MAC, &mac_addr) + .await?; + + // Set the raw socket RX/TX buffer sizes to 16KB + bus.write_frame(RegisterBlock::Socket0, socket::TXBUF_SIZE, &[16]) + .await?; + bus.write_frame(RegisterBlock::Socket0, socket::RXBUF_SIZE, &[16]) + .await?; + + // MACRAW mode with MAC filtering. + let mode: u8 = (1 << 2) | (1 << 7); + bus.write_frame(RegisterBlock::Socket0, socket::MODE, &[mode]) + .await?; + socket::command(&mut bus, socket::Command::Open).await?; + + Ok(Self { bus }) + } + + /// Read bytes from the RX buffer. Returns the number of bytes read. + async fn read_bytes(&mut self, buffer: &mut [u8], offset: u16) -> Result { + let rx_size = socket::get_rx_size(&mut self.bus).await? as usize; + + let read_buffer = if rx_size > buffer.len() + offset as usize { + buffer + } else { + &mut buffer[..rx_size - offset as usize] + }; + + let read_ptr = socket::get_rx_read_ptr(&mut self.bus) + .await? + .wrapping_add(offset); + self.bus + .read_frame(RegisterBlock::RxBuf, read_ptr, read_buffer) + .await?; + socket::set_rx_read_ptr( + &mut self.bus, + read_ptr.wrapping_add(read_buffer.len() as u16), + ) + .await?; + + Ok(read_buffer.len()) + } + + /// Read an ethernet frame from the device. Returns the number of bytes read. + pub async fn read_frame(&mut self, frame: &mut [u8]) -> Result { + let rx_size = socket::get_rx_size(&mut self.bus).await? as usize; + if rx_size == 0 { + return Ok(0); + } + + socket::reset_interrupt(&mut self.bus, socket::Interrupt::Receive).await?; + + // First two bytes gives the size of the received ethernet frame + let expected_frame_size: usize = { + let mut frame_bytes = [0u8; 2]; + assert!(self.read_bytes(&mut frame_bytes[..], 0).await? == 2); + u16::from_be_bytes(frame_bytes) as usize - 2 + }; + + // Read the ethernet frame + let read_buffer = if frame.len() > expected_frame_size { + &mut frame[..expected_frame_size] + } else { + frame + }; + + let recvd_frame_size = self.read_bytes(read_buffer, 2).await?; + + // Register RX as completed + socket::command(&mut self.bus, socket::Command::Receive).await?; + + // If the whole frame wasn't read, drop it + if recvd_frame_size < expected_frame_size { + Ok(0) + } else { + Ok(recvd_frame_size) + } + } + + /// Write an ethernet frame to the device. Returns number of bytes written + pub async fn write_frame(&mut self, frame: &[u8]) -> Result { + let max_size = socket::get_tx_free_size(&mut self.bus).await? as usize; + + let write_data = if frame.len() < max_size { + frame + } else { + &frame[..max_size] + }; + + let write_ptr = socket::get_tx_write_ptr(&mut self.bus).await?; + self.bus + .write_frame(RegisterBlock::TxBuf, write_ptr, write_data) + .await?; + socket::set_tx_write_ptr( + &mut self.bus, + write_ptr.wrapping_add(write_data.len() as u16), + ) + .await?; + + socket::reset_interrupt(&mut self.bus, socket::Interrupt::SendOk).await?; + socket::command(&mut self.bus, socket::Command::Send).await?; + // Wait for TX to complete + while !socket::is_interrupt(&mut self.bus, socket::Interrupt::SendOk).await? {} + socket::reset_interrupt(&mut self.bus, socket::Interrupt::SendOk).await?; + + Ok(write_data.len()) + } + + pub async fn is_link_up(&mut self) -> bool { + let mut link = [0]; + self.bus + .read_frame(RegisterBlock::Common, PHY_CFG, &mut link) + .await + .ok(); + link[0] & 1 == 1 + } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 00000000..bf14b05b --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,114 @@ +#![no_std] +/// [`embassy-net`](crates.io/crates/embassy-net) driver for the WIZnet W5500 ethernet chip. +mod device; +mod socket; +mod spi; + +use crate::device::W5500; +use embassy_futures::select::{select, Either}; +use embassy_net_driver_channel as ch; +use embassy_net_driver_channel::driver::LinkState; +use embassy_time::{Duration, Timer}; +use embedded_hal::digital::OutputPin; +use embedded_hal_async::digital::Wait; +use embedded_hal_async::spi::SpiDevice; +const MTU: usize = 1514; + +/// Type alias for the embassy-net driver for W5500 +pub type Device<'d> = embassy_net_driver_channel::Device<'d, MTU>; + +/// Internal state for the embassy-net integration. +pub struct State { + ch_state: ch::State, +} + +impl State { + /// Create a new `State`. + pub const fn new() -> Self { + Self { + ch_state: ch::State::new(), + } + } +} + +/// Background runner for the W5500. +/// +/// You must call `.run()` in a background task for the W5500 to operate. +pub struct Runner<'d, SPI: SpiDevice, INT: Wait, RST: OutputPin> { + mac: W5500, + ch: ch::Runner<'d, MTU>, + int: INT, + _reset: RST, +} + +/// You must call this in a background task for the W5500 to operate. +impl<'d, SPI: SpiDevice, INT: Wait, RST: OutputPin> Runner<'d, SPI, INT, RST> { + pub async fn run(mut self) -> ! { + let (state_chan, mut rx_chan, mut tx_chan) = self.ch.split(); + loop { + if self.mac.is_link_up().await { + state_chan.set_link_state(LinkState::Up); + loop { + match select( + async { + self.int.wait_for_low().await.ok(); + rx_chan.rx_buf().await + }, + tx_chan.tx_buf(), + ) + .await + { + Either::First(p) => { + if let Ok(n) = self.mac.read_frame(p).await { + rx_chan.rx_done(n); + } + } + Either::Second(p) => { + self.mac.write_frame(p).await.ok(); + tx_chan.tx_done(); + } + } + } + } else { + state_chan.set_link_state(LinkState::Down); + } + } + } +} + +/// Obtain a driver for using the W5500 with [`embassy-net`](crates.io/crates/embassy-net). +pub async fn new< + 'a, + const N_RX: usize, + const N_TX: usize, + SPI: SpiDevice, + INT: Wait, + RST: OutputPin, +>( + mac_addr: [u8; 6], + state: &'a mut State, + spi_dev: SPI, + int: INT, + mut reset: RST, +) -> (Device<'a>, Runner<'a, SPI, INT, RST>) { + // Reset the W5500. + reset.set_low().ok(); + // Ensure the reset is registered. + Timer::after(Duration::from_millis(1)).await; + reset.set_high().ok(); + // Wait for the W5500 to achieve PLL lock. + Timer::after(Duration::from_millis(2)).await; + + let mac = W5500::new(spi_dev, mac_addr).await.unwrap(); + + let (runner, device) = ch::new(&mut state.ch_state, mac_addr); + ( + device, + Runner { + ch: runner, + mac, + int, + _reset: reset, + }, + ) +} diff --git a/src/socket.rs b/src/socket.rs new file mode 100644 index 00000000..0d3d1aeb --- /dev/null +++ b/src/socket.rs @@ -0,0 +1,114 @@ +use crate::device::RegisterBlock; +use crate::spi::SpiInterface; +use embedded_hal_async::spi::SpiDevice; + +pub const MODE: u16 = 0x00; +pub const COMMAND: u16 = 0x01; +pub const RXBUF_SIZE: u16 = 0x1E; +pub const TXBUF_SIZE: u16 = 0x1F; +pub const TX_FREE_SIZE: u16 = 0x20; +pub const TX_DATA_WRITE_PTR: u16 = 0x24; +pub const RECVD_SIZE: u16 = 0x26; +pub const RX_DATA_READ_PTR: u16 = 0x28; +pub const SOCKET_INTR_MASK: u16 = 0x2C; + +#[repr(u8)] +pub enum Command { + Open = 0x01, + Send = 0x20, + Receive = 0x40, +} + +pub const INTR: u16 = 0x02; +#[repr(u8)] +pub enum Interrupt { + SendOk = 0b010000_u8, + Receive = 0b00100_u8, +} + +pub async fn reset_interrupt( + bus: &mut SpiInterface, + code: Interrupt, +) -> Result<(), SPI::Error> { + let data = [code as u8]; + bus.write_frame(RegisterBlock::Socket0, INTR, &data).await +} + +pub async fn is_interrupt( + bus: &mut SpiInterface, + code: Interrupt, +) -> Result { + let mut data = [0u8]; + bus.read_frame(RegisterBlock::Socket0, INTR, &mut data) + .await?; + Ok(data[0] & code as u8 != 0) +} + +pub async fn get_tx_write_ptr( + bus: &mut SpiInterface, +) -> Result { + let mut data = [0u8; 2]; + bus.read_frame(RegisterBlock::Socket0, TX_DATA_WRITE_PTR, &mut data) + .await?; + Ok(u16::from_be_bytes(data)) +} + +pub async fn set_tx_write_ptr( + bus: &mut SpiInterface, + ptr: u16, +) -> Result<(), SPI::Error> { + let data = ptr.to_be_bytes(); + bus.write_frame(RegisterBlock::Socket0, TX_DATA_WRITE_PTR, &data) + .await +} + +pub async fn get_rx_read_ptr( + bus: &mut SpiInterface, +) -> Result { + let mut data = [0u8; 2]; + bus.read_frame(RegisterBlock::Socket0, RX_DATA_READ_PTR, &mut data) + .await?; + Ok(u16::from_be_bytes(data)) +} + +pub async fn set_rx_read_ptr( + bus: &mut SpiInterface, + ptr: u16, +) -> Result<(), SPI::Error> { + let data = ptr.to_be_bytes(); + bus.write_frame(RegisterBlock::Socket0, RX_DATA_READ_PTR, &data) + .await +} + +pub async fn command( + bus: &mut SpiInterface, + command: Command, +) -> Result<(), SPI::Error> { + let data = [command as u8]; + bus.write_frame(RegisterBlock::Socket0, COMMAND, &data) + .await +} + +pub async fn get_rx_size(bus: &mut SpiInterface) -> Result { + loop { + // Wait until two sequential reads are equal + let mut res0 = [0u8; 2]; + bus.read_frame(RegisterBlock::Socket0, RECVD_SIZE, &mut res0) + .await?; + let mut res1 = [0u8; 2]; + bus.read_frame(RegisterBlock::Socket0, RECVD_SIZE, &mut res1) + .await?; + if res0 == res1 { + break Ok(u16::from_be_bytes(res0)); + } + } +} + +pub async fn get_tx_free_size( + bus: &mut SpiInterface, +) -> Result { + let mut data = [0; 2]; + bus.read_frame(RegisterBlock::Socket0, TX_FREE_SIZE, &mut data) + .await?; + Ok(u16::from_be_bytes(data)) +} diff --git a/src/spi.rs b/src/spi.rs new file mode 100644 index 00000000..55d31188 --- /dev/null +++ b/src/spi.rs @@ -0,0 +1,37 @@ +use crate::device::RegisterBlock; +use embedded_hal_async::spi::{Operation, SpiDevice}; + +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct SpiInterface(pub SPI); + +impl SpiInterface { + pub async fn read_frame( + &mut self, + block: RegisterBlock, + address: u16, + data: &mut [u8], + ) -> Result<(), SPI::Error> { + let address_phase = address.to_be_bytes(); + let control_phase = [(block as u8) << 3]; + let operations = &mut [ + Operation::Write(&address_phase), + Operation::Write(&control_phase), + Operation::TransferInPlace(data), + ]; + self.0.transaction(operations).await + } + + pub async fn write_frame( + &mut self, + block: RegisterBlock, + address: u16, + data: &[u8], + ) -> Result<(), SPI::Error> { + let address_phase = address.to_be_bytes(); + let control_phase = [(block as u8) << 3 | 0b0000_0100]; + let data_phase = data; + let operations = &[&address_phase[..], &control_phase, &data_phase]; + self.0.write_transaction(operations).await + } +} From fdc87a8e7f70e5246ab608df931a9623fb0289b4 Mon Sep 17 00:00:00 2001 From: kalkyl Date: Tue, 9 May 2023 02:21:17 +0200 Subject: [PATCH 126/178] Add CI --- .github/workflows/rust.yml | 29 +++++++++++++++++++++++++++++ ci.sh | 18 ++++++++++++++++++ 2 files changed, 47 insertions(+) create mode 100644 .github/workflows/rust.yml create mode 100644 ci.sh diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml new file mode 100644 index 00000000..dfa96dd0 --- /dev/null +++ b/.github/workflows/rust.yml @@ -0,0 +1,29 @@ +name: Rust + +on: + push: + branches: [main] + pull_request: + branches: [main] + merge_group: + +env: + CARGO_TERM_COLOR: always + +jobs: + build-nightly: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions/cache@v2 + with: + path: | + ~/.cargo/registry + ~/.cargo/git + target + key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} + + - name: Check fmt + run: cargo fmt -- --check + - name: Build + run: ./ci.sh diff --git a/ci.sh b/ci.sh new file mode 100644 index 00000000..74610ff2 --- /dev/null +++ b/ci.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +set -euxo pipefail + +export DEFMT_LOG=trace + +# build examples +#================== + +(cd examples; cargo build --bin multisocket --release) +(cd examples; cargo build --bin tcp-client --release) +(cd examples; cargo build --bin tcp-server --release) +(cd examples; cargo build --bin tcp-udp --release) + +# build lib +#============ + +cargo build --target thumbv6m-none-eabi From 9d018a0075ee3390c5fd7c8af5da3bb8a8787c7a Mon Sep 17 00:00:00 2001 From: kalkyl Date: Tue, 9 May 2023 02:24:25 +0200 Subject: [PATCH 127/178] Add CI --- ci.sh | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 ci.sh diff --git a/ci.sh b/ci.sh old mode 100644 new mode 100755 From d40589f08216c1fce6b9c8d8ad74d18fb5e58285 Mon Sep 17 00:00:00 2001 From: kalkyl Date: Tue, 9 May 2023 02:27:28 +0200 Subject: [PATCH 128/178] Fix CI --- ci.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ci.sh b/ci.sh index 74610ff2..0a287698 100755 --- a/ci.sh +++ b/ci.sh @@ -10,7 +10,7 @@ export DEFMT_LOG=trace (cd examples; cargo build --bin multisocket --release) (cd examples; cargo build --bin tcp-client --release) (cd examples; cargo build --bin tcp-server --release) -(cd examples; cargo build --bin tcp-udp --release) +(cd examples; cargo build --bin udp --release) # build lib #============ From adefa4f86bd3f5da3cfe85c618603abba54921ea Mon Sep 17 00:00:00 2001 From: kalkyl Date: Wed, 10 May 2023 02:23:54 +0200 Subject: [PATCH 129/178] vscode settings --- .vscode/settings.json | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/.vscode/settings.json b/.vscode/settings.json index 11fb4092..af1bd7b5 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,4 +1,15 @@ { + "editor.formatOnSave": true, + "[toml]": { + "editor.formatOnSave": false + }, + "rust-analyzer.check.allTargets": false, + "rust-analyzer.check.noDefaultFeatures": true, + "rust-analyzer.cargo.noDefaultFeatures": true, + "rust-analyzer.cargo.target": "thumbv6m-none-eabi", + "rust-analyzer.cargo.features": [ + "nightly", + ], "rust-analyzer.linkedProjects": [ ".\\examples\\Cargo.toml" ] From 7b83d53bbfb5be30415d966504eacbd6f4c1cc90 Mon Sep 17 00:00:00 2001 From: kalkyl Date: Wed, 10 May 2023 02:25:18 +0200 Subject: [PATCH 130/178] vscode settings --- .vscode/settings.json | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index af1bd7b5..231c407a 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,15 +1,12 @@ { "editor.formatOnSave": true, "[toml]": { - "editor.formatOnSave": false + "editor.formatOnSave": false }, "rust-analyzer.check.allTargets": false, "rust-analyzer.check.noDefaultFeatures": true, "rust-analyzer.cargo.noDefaultFeatures": true, "rust-analyzer.cargo.target": "thumbv6m-none-eabi", - "rust-analyzer.cargo.features": [ - "nightly", - ], "rust-analyzer.linkedProjects": [ ".\\examples\\Cargo.toml" ] From bbd687fcb0e63a1bb8eb4d31c8f5ed2f403603f6 Mon Sep 17 00:00:00 2001 From: kalkyl Date: Wed, 10 May 2023 02:40:41 +0200 Subject: [PATCH 131/178] Update embassy --- Cargo.toml | 16 ++++++++-------- examples/Cargo.toml | 18 +++++++++--------- rust-toolchain.toml | 2 +- 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index ac2257f4..1921e812 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,11 +16,11 @@ embassy-futures = { version = "0.1.0" } defmt = { version = "0.3", optional = true } [patch.crates-io] -embassy-executor = { git = "https://github.com/embassy-rs/embassy", rev = "03d6363d5af5dcaf21b52734994a466ca593d2b6" } -embassy-time = { git = "https://github.com/embassy-rs/embassy", rev = "03d6363d5af5dcaf21b52734994a466ca593d2b6" } -embassy-futures = { git = "https://github.com/embassy-rs/embassy", rev = "03d6363d5af5dcaf21b52734994a466ca593d2b6" } -embassy-sync = { git = "https://github.com/embassy-rs/embassy", rev = "03d6363d5af5dcaf21b52734994a466ca593d2b6" } -embassy-rp = { git = "https://github.com/embassy-rs/embassy", rev = "03d6363d5af5dcaf21b52734994a466ca593d2b6" } -embassy-net = { git = "https://github.com/embassy-rs/embassy", rev = "03d6363d5af5dcaf21b52734994a466ca593d2b6" } -embassy-net-driver = { git = "https://github.com/embassy-rs/embassy", rev = "03d6363d5af5dcaf21b52734994a466ca593d2b6" } -embassy-net-driver-channel = { git = "https://github.com/embassy-rs/embassy", rev = "03d6363d5af5dcaf21b52734994a466ca593d2b6" } +embassy-executor = { git = "https://github.com/embassy-rs/embassy", rev = "e179e7cf85810f0aa7ef8027d8d48f6d21f64dac" } +embassy-time = { git = "https://github.com/embassy-rs/embassy", rev = "e179e7cf85810f0aa7ef8027d8d48f6d21f64dac" } +embassy-futures = { git = "https://github.com/embassy-rs/embassy", rev = "e179e7cf85810f0aa7ef8027d8d48f6d21f64dac" } +embassy-sync = { git = "https://github.com/embassy-rs/embassy", rev = "e179e7cf85810f0aa7ef8027d8d48f6d21f64dac" } +embassy-rp = { git = "https://github.com/embassy-rs/embassy", rev = "e179e7cf85810f0aa7ef8027d8d48f6d21f64dac" } +embassy-net = { git = "https://github.com/embassy-rs/embassy", rev = "e179e7cf85810f0aa7ef8027d8d48f6d21f64dac" } +embassy-net-driver = { git = "https://github.com/embassy-rs/embassy", rev = "e179e7cf85810f0aa7ef8027d8d48f6d21f64dac" } +embassy-net-driver-channel = { git = "https://github.com/embassy-rs/embassy", rev = "e179e7cf85810f0aa7ef8027d8d48f6d21f64dac" } diff --git a/examples/Cargo.toml b/examples/Cargo.toml index 013a2755..46659c2b 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" edition = "2021" [dependencies] -embassy-executor = { version = "0.1.0", features = ["defmt", "integrated-timers", "executor-thread", "arch-cortex-m"] } +embassy-executor = { version = "0.2.0", features = ["defmt", "integrated-timers", "executor-thread", "arch-cortex-m"] } embassy-time = { version = "0.1.0", features = ["defmt", "defmt-timestamp-uptime"] } embassy-rp = { version = "0.1.0", features = ["defmt", "unstable-traits", "nightly", "unstable-pac", "time-driver"] } embassy-net = { version = "0.1.0", features = ["defmt", "tcp", "udp", "dhcpv4", "medium-ethernet", "unstable-traits", "nightly"] } @@ -30,14 +30,14 @@ rand = { version = "0.8.5", default-features = false } embassy-net-w5500 = { path = "../" } [patch.crates-io] -embassy-executor = { git = "https://github.com/embassy-rs/embassy", rev = "03d6363d5af5dcaf21b52734994a466ca593d2b6" } -embassy-time = { git = "https://github.com/embassy-rs/embassy", rev = "03d6363d5af5dcaf21b52734994a466ca593d2b6" } -embassy-futures = { git = "https://github.com/embassy-rs/embassy", rev = "03d6363d5af5dcaf21b52734994a466ca593d2b6" } -embassy-sync = { git = "https://github.com/embassy-rs/embassy", rev = "03d6363d5af5dcaf21b52734994a466ca593d2b6" } -embassy-rp = { git = "https://github.com/embassy-rs/embassy", rev = "03d6363d5af5dcaf21b52734994a466ca593d2b6" } -embassy-net = { git = "https://github.com/embassy-rs/embassy", rev = "03d6363d5af5dcaf21b52734994a466ca593d2b6" } -embassy-net-driver = { git = "https://github.com/embassy-rs/embassy", rev = "03d6363d5af5dcaf21b52734994a466ca593d2b6" } -embassy-net-driver-channel = { git = "https://github.com/embassy-rs/embassy", rev = "03d6363d5af5dcaf21b52734994a466ca593d2b6" } +embassy-executor = { git = "https://github.com/embassy-rs/embassy", rev = "e179e7cf85810f0aa7ef8027d8d48f6d21f64dac" } +embassy-time = { git = "https://github.com/embassy-rs/embassy", rev = "e179e7cf85810f0aa7ef8027d8d48f6d21f64dac" } +embassy-futures = { git = "https://github.com/embassy-rs/embassy", rev = "e179e7cf85810f0aa7ef8027d8d48f6d21f64dac" } +embassy-sync = { git = "https://github.com/embassy-rs/embassy", rev = "e179e7cf85810f0aa7ef8027d8d48f6d21f64dac" } +embassy-rp = { git = "https://github.com/embassy-rs/embassy", rev = "e179e7cf85810f0aa7ef8027d8d48f6d21f64dac" } +embassy-net = { git = "https://github.com/embassy-rs/embassy", rev = "e179e7cf85810f0aa7ef8027d8d48f6d21f64dac" } +embassy-net-driver = { git = "https://github.com/embassy-rs/embassy", rev = "e179e7cf85810f0aa7ef8027d8d48f6d21f64dac" } +embassy-net-driver-channel = { git = "https://github.com/embassy-rs/embassy", rev = "e179e7cf85810f0aa7ef8027d8d48f6d21f64dac" } [profile.dev] debug = 2 diff --git a/rust-toolchain.toml b/rust-toolchain.toml index fb284c1e..2582e88f 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,7 +1,7 @@ # Before upgrading check that everything is available on all tier1 targets here: # https://rust-lang.github.io/rustup-components-history [toolchain] -channel = "nightly-2023-04-04" +channel = "nightly-2023-04-18" components = [ "rust-src", "rustfmt" ] targets = [ "thumbv6m-none-eabi", From 8800caa216f2c90b7d998280a54dddf14e97e318 Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Sat, 13 May 2023 02:20:46 +0200 Subject: [PATCH 132/178] Update Embassy, to new PIO API. --- Cargo.toml | 12 +- cyw43-pio/Cargo.toml | 1 + cyw43-pio/src/lib.rs | 151 ++++++++++--------- examples/rpi-pico-w/Cargo.toml | 18 +-- examples/rpi-pico-w/src/bin/tcp_server.rs | 16 +- examples/rpi-pico-w/src/bin/tcp_server_ap.rs | 16 +- examples/rpi-pico-w/src/bin/wifi_scan.rs | 16 +- 7 files changed, 112 insertions(+), 118 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index c4872f42..2bb2b8d9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,7 +12,7 @@ firmware-logs = [] [dependencies] embassy-time = { version = "0.1.0" } -embassy-sync = { version = "0.1.0" } +embassy-sync = { version = "0.2.0" } embassy-futures = { version = "0.1.0" } embassy-net-driver-channel = { version = "0.1.0" } atomic-polyfill = "0.1.5" @@ -28,11 +28,11 @@ embedded-hal-1 = { package = "embedded-hal", version = "1.0.0-alpha.10" } num_enum = { version = "0.5.7", default-features = false } [patch.crates-io] -embassy-time = { git = "https://github.com/embassy-rs/embassy", rev = "03d6363d5af5dcaf21b52734994a466ca593d2b6" } -embassy-futures = { git = "https://github.com/embassy-rs/embassy", rev = "03d6363d5af5dcaf21b52734994a466ca593d2b6" } -embassy-sync = { git = "https://github.com/embassy-rs/embassy", rev = "03d6363d5af5dcaf21b52734994a466ca593d2b6" } -embassy-net-driver-channel = { git = "https://github.com/embassy-rs/embassy", rev = "03d6363d5af5dcaf21b52734994a466ca593d2b6" } -embassy-rp = { git = "https://github.com/embassy-rs/embassy", rev = "03d6363d5af5dcaf21b52734994a466ca593d2b6" } +embassy-time = { git = "https://github.com/embassy-rs/embassy", rev = "82f7e104d90a6628d1873017ea5ef6a7afb3b3f7" } +embassy-futures = { git = "https://github.com/embassy-rs/embassy", rev = "82f7e104d90a6628d1873017ea5ef6a7afb3b3f7" } +embassy-sync = { git = "https://github.com/embassy-rs/embassy", rev = "82f7e104d90a6628d1873017ea5ef6a7afb3b3f7" } +embassy-net-driver-channel = { git = "https://github.com/embassy-rs/embassy", rev = "82f7e104d90a6628d1873017ea5ef6a7afb3b3f7" } +embassy-rp = { git = "https://github.com/embassy-rs/embassy", rev = "82f7e104d90a6628d1873017ea5ef6a7afb3b3f7" } [workspace] members = ["cyw43-pio"] diff --git a/cyw43-pio/Cargo.toml b/cyw43-pio/Cargo.toml index 8272903c..dd50e02b 100644 --- a/cyw43-pio/Cargo.toml +++ b/cyw43-pio/Cargo.toml @@ -8,4 +8,5 @@ cyw43 = { path = "../" } embassy-rp = { version = "0.1.0", features = ["unstable-traits", "nightly", "unstable-pac", "time-driver"] } pio-proc = "0.2" pio = "0.2.1" +fixed = "1.23.1" defmt = { version = "0.3", optional = true } \ No newline at end of file diff --git a/cyw43-pio/src/lib.rs b/cyw43-pio/src/lib.rs index c468435f..2c17b151 100644 --- a/cyw43-pio/src/lib.rs +++ b/cyw43-pio/src/lib.rs @@ -6,30 +6,39 @@ use core::slice; use cyw43::SpiBusCyw43; use embassy_rp::dma::Channel; -use embassy_rp::gpio::{Drive, Output, Pin, Pull, SlewRate}; -use embassy_rp::pio::{PioStateMachine, ShiftDirection}; +use embassy_rp::gpio::{Drive, Level, Output, Pin, Pull, SlewRate}; +use embassy_rp::pio::{Common, Config, Direction, Instance, Irq, PioPin, ShiftDirection, StateMachine}; use embassy_rp::relocate::RelocatedProgram; -use embassy_rp::{pio_instr_util, Peripheral}; -use pio::Wrap; +use embassy_rp::{pio_instr_util, Peripheral, PeripheralRef}; +use fixed::FixedU32; use pio_proc::pio_asm; -pub struct PioSpi { - cs: Output<'static, CS>, - sm: SM, - dma: DMA, +pub struct PioSpi<'d, CS: Pin, PIO: Instance, const SM: usize, DMA> { + cs: Output<'d, CS>, + sm: StateMachine<'d, PIO, SM>, + irq: Irq<'d, PIO, 0>, + dma: PeripheralRef<'d, DMA>, wrap_target: u8, } -impl PioSpi +impl<'d, CS, PIO, const SM: usize, DMA> PioSpi<'d, CS, PIO, SM, DMA> where - SM: PioStateMachine, DMA: Channel, CS: Pin, + PIO: Instance, { - pub fn new(mut sm: SM, cs: Output<'static, CS>, dio: DIO, clk: CLK, dma: DMA) -> Self + pub fn new( + common: &mut Common<'d, PIO>, + mut sm: StateMachine<'d, PIO, SM>, + irq: Irq<'d, PIO, 0>, + cs: Output<'d, CS>, + dio: DIO, + clk: CLK, + dma: impl Peripheral

+ 'd, + ) -> Self where - DIO: Pin, - CLK: Pin, + DIO: PioPin, + CLK: PioPin, { let program = pio_asm!( ".side_set 1" @@ -42,8 +51,8 @@ where // switch directions "set pindirs, 0 side 0" // these nops seem to be necessary for fast clkdiv - "nop side 1" - "nop side 0" + //"nop side 1" + //"nop side 0" "nop side 1" // read in y-1 bits "lp2:" @@ -59,68 +68,62 @@ where let relocated = RelocatedProgram::new(&program.program); - let mut pin_io = sm.make_pio_pin(dio); - pin_io.set_pull(Pull::Down); + let mut pin_io: embassy_rp::pio::Pin = common.make_pio_pin(dio); + pin_io.set_pull(Pull::None); pin_io.set_schmitt(true); pin_io.set_input_sync_bypass(true); + //pin_io.set_drive_strength(Drive::_12mA); + //pin_io.set_slew_rate(SlewRate::Fast); - let mut pin_clk = sm.make_pio_pin(clk); + let mut pin_clk = common.make_pio_pin(clk); pin_clk.set_drive_strength(Drive::_12mA); pin_clk.set_slew_rate(SlewRate::Fast); - sm.write_instr(relocated.origin() as usize, relocated.code()); + let mut cfg = Config::default(); + cfg.use_program(&common.load_program(&relocated), &[&pin_clk]); + cfg.set_out_pins(&[&pin_io]); + cfg.set_in_pins(&[&pin_io]); + cfg.set_set_pins(&[&pin_io]); + cfg.shift_out.direction = ShiftDirection::Left; + cfg.shift_out.auto_fill = true; + //cfg.shift_out.threshold = 32; + cfg.shift_in.direction = ShiftDirection::Left; + cfg.shift_in.auto_fill = true; + //cfg.shift_in.threshold = 32; // theoretical maximum according to data sheet, 100Mhz Pio => 50Mhz SPI Freq - sm.set_clkdiv(0x0140); + // seems to cause random corruption, probably due to jitter due to the fractional divider. + // cfg.clock_divider = FixedU32::from_bits(0x0140); // same speed as pico-sdk, 62.5Mhz - // sm.set_clkdiv(0x0200); + cfg.clock_divider = FixedU32::from_bits(0x0200); // 32 Mhz - // sm.set_clkdiv(0x03E8); + // cfg.clock_divider = FixedU32::from_bits(0x03E8); // 16 Mhz - // sm.set_clkdiv(0x07d0); + // cfg.clock_divider = FixedU32::from_bits(0x07d0); // 8Mhz - // sm.set_clkdiv(0x0a_00); + // cfg.clock_divider = FixedU32::from_bits(0x0a_00); // 1Mhz - // sm.set_clkdiv(0x7d_00); + // cfg.clock_divider = FixedU32::from_bits(0x7d_00); // slowest possible - // sm.set_clkdiv(0xffff_00); + // cfg.clock_divider = FixedU32::from_bits(0xffff_00); - sm.set_autopull(true); - // sm.set_pull_threshold(32); - sm.set_autopush(true); - // sm.set_push_threshold(32); + sm.set_config(&cfg); - sm.set_out_pins(&[&pin_io]); - sm.set_in_base_pin(&pin_io); - - sm.set_set_pins(&[&pin_clk]); - pio_instr_util::set_pindir(&mut sm, 0b1); - sm.set_set_pins(&[&pin_io]); - pio_instr_util::set_pindir(&mut sm, 0b1); - - sm.set_sideset_base_pin(&pin_clk); - sm.set_sideset_count(1); - - sm.set_out_shift_dir(ShiftDirection::Left); - sm.set_in_shift_dir(ShiftDirection::Left); - - let Wrap { source, target } = relocated.wrap(); - sm.set_wrap(source, target); - - // pull low for startup - pio_instr_util::set_pin(&mut sm, 0); + sm.set_pin_dirs(Direction::Out, &[&pin_clk, &pin_io]); + sm.set_pins(Level::Low, &[&pin_clk, &pin_io]); Self { cs, sm, - dma, - wrap_target: target, + irq, + dma: dma.into_ref(), + wrap_target: relocated.wrap().target, } } @@ -132,18 +135,22 @@ where #[cfg(feature = "defmt")] defmt::trace!("write={} read={}", write_bits, read_bits); - let mut dma = Peripheral::into_ref(&mut self.dma); - pio_instr_util::set_x(&mut self.sm, write_bits as u32); - pio_instr_util::set_y(&mut self.sm, read_bits as u32); - pio_instr_util::set_pindir(&mut self.sm, 0b1); - pio_instr_util::exec_jmp(&mut self.sm, self.wrap_target); + unsafe { + pio_instr_util::set_x(&mut self.sm, write_bits as u32); + pio_instr_util::set_y(&mut self.sm, read_bits as u32); + pio_instr_util::set_pindir(&mut self.sm, 0b1); + pio_instr_util::exec_jmp(&mut self.sm, self.wrap_target); + } self.sm.set_enable(true); - self.sm.dma_push(dma.reborrow(), write).await; + self.sm.tx().dma_push(self.dma.reborrow(), write).await; let mut status = 0; - self.sm.dma_pull(dma.reborrow(), slice::from_mut(&mut status)).await; + self.sm + .rx() + .dma_pull(self.dma.reborrow(), slice::from_mut(&mut status)) + .await; status } @@ -155,27 +162,32 @@ where #[cfg(feature = "defmt")] defmt::trace!("write={} read={}", write_bits, read_bits); - let mut dma = Peripheral::into_ref(&mut self.dma); - pio_instr_util::set_y(&mut self.sm, read_bits as u32); - pio_instr_util::set_x(&mut self.sm, write_bits as u32); - pio_instr_util::set_pindir(&mut self.sm, 0b1); - pio_instr_util::exec_jmp(&mut self.sm, self.wrap_target); + unsafe { + pio_instr_util::set_y(&mut self.sm, read_bits as u32); + pio_instr_util::set_x(&mut self.sm, write_bits as u32); + pio_instr_util::set_pindir(&mut self.sm, 0b1); + pio_instr_util::exec_jmp(&mut self.sm, self.wrap_target); + } + // self.cs.set_low(); self.sm.set_enable(true); - self.sm.dma_push(dma.reborrow(), slice::from_ref(&cmd)).await; - self.sm.dma_pull(dma.reborrow(), read).await; + self.sm.tx().dma_push(self.dma.reborrow(), slice::from_ref(&cmd)).await; + self.sm.rx().dma_pull(self.dma.reborrow(), read).await; let mut status = 0; - self.sm.dma_pull(dma.reborrow(), slice::from_mut(&mut status)).await; + self.sm + .rx() + .dma_pull(self.dma.reborrow(), slice::from_mut(&mut status)) + .await; status } } -impl SpiBusCyw43 for PioSpi +impl<'d, CS, PIO, const SM: usize, DMA> SpiBusCyw43 for PioSpi<'d, CS, PIO, SM, DMA> where CS: Pin, - SM: PioStateMachine, + PIO: Instance, DMA: Channel, { async fn cmd_write(&mut self, write: &[u32]) -> u32 { @@ -193,7 +205,6 @@ where } async fn wait_for_event(&mut self) { - self.sm.wait_irq(0).await; - self.sm.clear_irq(0); + self.irq.wait().await; } } diff --git a/examples/rpi-pico-w/Cargo.toml b/examples/rpi-pico-w/Cargo.toml index 5b46726d..d972bf5a 100644 --- a/examples/rpi-pico-w/Cargo.toml +++ b/examples/rpi-pico-w/Cargo.toml @@ -7,7 +7,7 @@ edition = "2021" [dependencies] cyw43 = { path = "../../", features = ["defmt", "firmware-logs"] } cyw43-pio = { path = "../../cyw43-pio", features = ["defmt"] } -embassy-executor = { version = "0.1.0", features = ["defmt", "integrated-timers", "executor-thread", "arch-cortex-m"] } +embassy-executor = { version = "0.2.0", features = ["defmt", "integrated-timers", "executor-thread", "arch-cortex-m"] } embassy-time = { version = "0.1.0", features = ["defmt", "defmt-timestamp-uptime"] } embassy-rp = { version = "0.1.0", features = ["defmt", "unstable-traits", "nightly", "unstable-pac", "time-driver"] } embassy-net = { version = "0.1.0", features = ["defmt", "tcp", "dhcpv4", "medium-ethernet", "unstable-traits", "nightly"] } @@ -27,14 +27,14 @@ heapless = "0.7.15" [patch.crates-io] -embassy-executor = { git = "https://github.com/embassy-rs/embassy", rev = "03d6363d5af5dcaf21b52734994a466ca593d2b6" } -embassy-time = { git = "https://github.com/embassy-rs/embassy", rev = "03d6363d5af5dcaf21b52734994a466ca593d2b6" } -embassy-futures = { git = "https://github.com/embassy-rs/embassy", rev = "03d6363d5af5dcaf21b52734994a466ca593d2b6" } -embassy-sync = { git = "https://github.com/embassy-rs/embassy", rev = "03d6363d5af5dcaf21b52734994a466ca593d2b6" } -embassy-rp = { git = "https://github.com/embassy-rs/embassy", rev = "03d6363d5af5dcaf21b52734994a466ca593d2b6" } -embassy-net = { git = "https://github.com/embassy-rs/embassy", rev = "03d6363d5af5dcaf21b52734994a466ca593d2b6" } -embassy-net-driver = { git = "https://github.com/embassy-rs/embassy", rev = "03d6363d5af5dcaf21b52734994a466ca593d2b6" } -embassy-net-driver-channel = { git = "https://github.com/embassy-rs/embassy", rev = "03d6363d5af5dcaf21b52734994a466ca593d2b6" } +embassy-executor = { git = "https://github.com/embassy-rs/embassy", rev = "82f7e104d90a6628d1873017ea5ef6a7afb3b3f7" } +embassy-time = { git = "https://github.com/embassy-rs/embassy", rev = "82f7e104d90a6628d1873017ea5ef6a7afb3b3f7" } +embassy-futures = { git = "https://github.com/embassy-rs/embassy", rev = "82f7e104d90a6628d1873017ea5ef6a7afb3b3f7" } +embassy-sync = { git = "https://github.com/embassy-rs/embassy", rev = "82f7e104d90a6628d1873017ea5ef6a7afb3b3f7" } +embassy-rp = { git = "https://github.com/embassy-rs/embassy", rev = "82f7e104d90a6628d1873017ea5ef6a7afb3b3f7" } +embassy-net = { git = "https://github.com/embassy-rs/embassy", rev = "82f7e104d90a6628d1873017ea5ef6a7afb3b3f7" } +embassy-net-driver = { git = "https://github.com/embassy-rs/embassy", rev = "82f7e104d90a6628d1873017ea5ef6a7afb3b3f7" } +embassy-net-driver-channel = { git = "https://github.com/embassy-rs/embassy", rev = "82f7e104d90a6628d1873017ea5ef6a7afb3b3f7" } [profile.dev] debug = 2 diff --git a/examples/rpi-pico-w/src/bin/tcp_server.rs b/examples/rpi-pico-w/src/bin/tcp_server.rs index 9581602a..8accc469 100644 --- a/examples/rpi-pico-w/src/bin/tcp_server.rs +++ b/examples/rpi-pico-w/src/bin/tcp_server.rs @@ -12,8 +12,8 @@ use embassy_executor::Spawner; use embassy_net::tcp::TcpSocket; use embassy_net::{Config, Stack, StackResources}; use embassy_rp::gpio::{Level, Output}; -use embassy_rp::peripherals::{DMA_CH0, PIN_23, PIN_25}; -use embassy_rp::pio::{Pio0, PioPeripheral, PioStateMachineInstance, Sm0}; +use embassy_rp::peripherals::{DMA_CH0, PIN_23, PIN_25, PIO0}; +use embassy_rp::pio::Pio; use embedded_io::asynch::Write; use static_cell::StaticCell; use {defmt_rtt as _, panic_probe as _}; @@ -28,11 +28,7 @@ macro_rules! singleton { #[embassy_executor::task] async fn wifi_task( - runner: cyw43::Runner< - 'static, - Output<'static, PIN_23>, - PioSpi, DMA_CH0>, - >, + runner: cyw43::Runner<'static, Output<'static, PIN_23>, PioSpi<'static, PIN_25, PIO0, 0, DMA_CH0>>, ) -> ! { runner.run().await } @@ -60,10 +56,8 @@ async fn main(spawner: Spawner) { let pwr = Output::new(p.PIN_23, Level::Low); let cs = Output::new(p.PIN_25, Level::High); - - let (_, sm, _, _, _) = p.PIO0.split(); - let dma = p.DMA_CH0; - let spi = PioSpi::new(sm, cs, p.PIN_24, p.PIN_29, dma); + let mut pio = Pio::new(p.PIO0); + let spi = PioSpi::new(&mut pio.common, pio.sm0, pio.irq0, cs, p.PIN_24, p.PIN_29, p.DMA_CH0); let state = singleton!(cyw43::State::new()); let (net_device, mut control, runner) = cyw43::new(state, pwr, spi, fw).await; diff --git a/examples/rpi-pico-w/src/bin/tcp_server_ap.rs b/examples/rpi-pico-w/src/bin/tcp_server_ap.rs index e4341262..ee2c3237 100644 --- a/examples/rpi-pico-w/src/bin/tcp_server_ap.rs +++ b/examples/rpi-pico-w/src/bin/tcp_server_ap.rs @@ -12,8 +12,8 @@ use embassy_executor::Spawner; use embassy_net::tcp::TcpSocket; use embassy_net::{Config, Stack, StackResources}; use embassy_rp::gpio::{Level, Output}; -use embassy_rp::peripherals::{DMA_CH0, PIN_23, PIN_25}; -use embassy_rp::pio::{Pio0, PioPeripheral, PioStateMachineInstance, Sm0}; +use embassy_rp::peripherals::{DMA_CH0, PIN_23, PIN_25, PIO0}; +use embassy_rp::pio::Pio; use embedded_io::asynch::Write; use static_cell::StaticCell; use {defmt_rtt as _, panic_probe as _}; @@ -28,11 +28,7 @@ macro_rules! singleton { #[embassy_executor::task] async fn wifi_task( - runner: cyw43::Runner< - 'static, - Output<'static, PIN_23>, - PioSpi, DMA_CH0>, - >, + runner: cyw43::Runner<'static, Output<'static, PIN_23>, PioSpi<'static, PIN_25, PIO0, 0, DMA_CH0>>, ) -> ! { runner.run().await } @@ -60,10 +56,8 @@ async fn main(spawner: Spawner) { let pwr = Output::new(p.PIN_23, Level::Low); let cs = Output::new(p.PIN_25, Level::High); - - let (_, sm, _, _, _) = p.PIO0.split(); - let dma = p.DMA_CH0; - let spi = PioSpi::new(sm, cs, p.PIN_24, p.PIN_29, dma); + let mut pio = Pio::new(p.PIO0); + let spi = PioSpi::new(&mut pio.common, pio.sm0, pio.irq0, cs, p.PIN_24, p.PIN_29, p.DMA_CH0); let state = singleton!(cyw43::State::new()); let (net_device, mut control, runner) = cyw43::new(state, pwr, spi, fw).await; diff --git a/examples/rpi-pico-w/src/bin/wifi_scan.rs b/examples/rpi-pico-w/src/bin/wifi_scan.rs index da8fadfd..a2a44f99 100644 --- a/examples/rpi-pico-w/src/bin/wifi_scan.rs +++ b/examples/rpi-pico-w/src/bin/wifi_scan.rs @@ -11,8 +11,8 @@ use defmt::*; use embassy_executor::Spawner; use embassy_net::Stack; use embassy_rp::gpio::{Level, Output}; -use embassy_rp::peripherals::{DMA_CH0, PIN_23, PIN_25}; -use embassy_rp::pio::{Pio0, PioPeripheral, PioStateMachineInstance, Sm0}; +use embassy_rp::peripherals::{DMA_CH0, PIN_23, PIN_25, PIO0}; +use embassy_rp::pio::Pio; use static_cell::StaticCell; use {defmt_rtt as _, panic_probe as _}; @@ -26,11 +26,7 @@ macro_rules! singleton { #[embassy_executor::task] async fn wifi_task( - runner: cyw43::Runner< - 'static, - Output<'static, PIN_23>, - PioSpi, DMA_CH0>, - >, + runner: cyw43::Runner<'static, Output<'static, PIN_23>, PioSpi<'static, PIN_25, PIO0, 0, DMA_CH0>>, ) -> ! { runner.run().await } @@ -58,10 +54,8 @@ async fn main(spawner: Spawner) { let pwr = Output::new(p.PIN_23, Level::Low); let cs = Output::new(p.PIN_25, Level::High); - - let (_, sm, _, _, _) = p.PIO0.split(); - let dma = p.DMA_CH0; - let spi = PioSpi::new(sm, cs, p.PIN_24, p.PIN_29, dma); + let mut pio = Pio::new(p.PIO0); + let spi = PioSpi::new(&mut pio.common, pio.sm0, pio.irq0, cs, p.PIN_24, p.PIN_29, p.DMA_CH0); let state = singleton!(cyw43::State::new()); let (_net_device, mut control, runner) = cyw43::new(state, pwr, spi, fw).await; From 6c1137177f92f9a5c5cd9c2a0450b5db5165f8be Mon Sep 17 00:00:00 2001 From: kalkyl Date: Sat, 13 May 2023 06:34:03 +0200 Subject: [PATCH 133/178] Wait until there's enough space in tx buffer, remove busy wait for completed send --- src/device.rs | 25 ++++--------------------- src/socket.rs | 11 ----------- 2 files changed, 4 insertions(+), 32 deletions(-) diff --git a/src/device.rs b/src/device.rs index 3875fde0..8158bc98 100644 --- a/src/device.rs +++ b/src/device.rs @@ -123,31 +123,14 @@ impl W5500 { /// Write an ethernet frame to the device. Returns number of bytes written pub async fn write_frame(&mut self, frame: &[u8]) -> Result { - let max_size = socket::get_tx_free_size(&mut self.bus).await? as usize; - - let write_data = if frame.len() < max_size { - frame - } else { - &frame[..max_size] - }; - + while socket::get_tx_free_size(&mut self.bus).await? < frame.len() as u16 {} let write_ptr = socket::get_tx_write_ptr(&mut self.bus).await?; self.bus - .write_frame(RegisterBlock::TxBuf, write_ptr, write_data) + .write_frame(RegisterBlock::TxBuf, write_ptr, frame) .await?; - socket::set_tx_write_ptr( - &mut self.bus, - write_ptr.wrapping_add(write_data.len() as u16), - ) - .await?; - - socket::reset_interrupt(&mut self.bus, socket::Interrupt::SendOk).await?; + socket::set_tx_write_ptr(&mut self.bus, write_ptr.wrapping_add(frame.len() as u16)).await?; socket::command(&mut self.bus, socket::Command::Send).await?; - // Wait for TX to complete - while !socket::is_interrupt(&mut self.bus, socket::Interrupt::SendOk).await? {} - socket::reset_interrupt(&mut self.bus, socket::Interrupt::SendOk).await?; - - Ok(write_data.len()) + Ok(frame.len()) } pub async fn is_link_up(&mut self) -> bool { diff --git a/src/socket.rs b/src/socket.rs index 0d3d1aeb..3f64d04d 100644 --- a/src/socket.rs +++ b/src/socket.rs @@ -22,7 +22,6 @@ pub enum Command { pub const INTR: u16 = 0x02; #[repr(u8)] pub enum Interrupt { - SendOk = 0b010000_u8, Receive = 0b00100_u8, } @@ -34,16 +33,6 @@ pub async fn reset_interrupt( bus.write_frame(RegisterBlock::Socket0, INTR, &data).await } -pub async fn is_interrupt( - bus: &mut SpiInterface, - code: Interrupt, -) -> Result { - let mut data = [0u8]; - bus.read_frame(RegisterBlock::Socket0, INTR, &mut data) - .await?; - Ok(data[0] & code as u8 != 0) -} - pub async fn get_tx_write_ptr( bus: &mut SpiInterface, ) -> Result { From f46e0eb5f28fff64997fee7fc15b123ddc7231fe Mon Sep 17 00:00:00 2001 From: Matt Johnston Date: Sun, 14 May 2023 22:48:04 +0800 Subject: [PATCH 134/178] Fix PowerManagementMode::None MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Mode was being set to 2 (PM2_POWERSAVE_MODE), should be 0 (NO_POWERSAVE_MODE). Setting None mode failed with a panic: 85.707099 DEBUG set pm2_sleep_ret = [00, 00, 00, 00] └─ cyw43::control::{impl#0}::set_iovar_v::{async_fn#0} @ cyw43/src/fmt.rs:127 85.710469 ERROR panicked at 'IOCTL error -29' --- src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lib.rs b/src/lib.rs index 4a9ada90..fd11f367 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -198,6 +198,7 @@ impl PowerManagementMode { fn mode(&self) -> u32 { match self { PowerManagementMode::ThroughputThrottling => 1, + PowerManagementMode::None => 0, _ => 2, } } From db907a914c7e84176a15da70385af871c9b97cf6 Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Sun, 14 May 2023 23:02:49 +0200 Subject: [PATCH 135/178] cyw43-pio: add `overclock` feature flag. --- ci.sh | 5 +++ cyw43-pio/Cargo.toml | 5 +++ cyw43-pio/src/lib.rs | 77 +++++++++++++++++++++------------- examples/rpi-pico-w/Cargo.toml | 4 +- 4 files changed, 60 insertions(+), 31 deletions(-) diff --git a/ci.sh b/ci.sh index d41b3aa8..91683820 100755 --- a/ci.sh +++ b/ci.sh @@ -1,7 +1,9 @@ #!/bin/bash set -euxo pipefail +cd $(dirname $0) +export CARGO_TARGET_DIR=$(pwd)/target export DEFMT_LOG=trace # build examples @@ -18,3 +20,6 @@ cargo build --target thumbv6m-none-eabi --features 'log' cargo build --target thumbv6m-none-eabi --features 'defmt' cargo build --target thumbv6m-none-eabi --features 'log,firmware-logs' cargo build --target thumbv6m-none-eabi --features 'defmt,firmware-logs' + +(cd cyw43-pio; cargo build --target thumbv6m-none-eabi --features '') +(cd cyw43-pio; cargo build --target thumbv6m-none-eabi --features 'overclock') diff --git a/cyw43-pio/Cargo.toml b/cyw43-pio/Cargo.toml index dd50e02b..2238f761 100644 --- a/cyw43-pio/Cargo.toml +++ b/cyw43-pio/Cargo.toml @@ -3,6 +3,11 @@ name = "cyw43-pio" version = "0.1.0" edition = "2021" +[features] +# If disabled, SPI runs at 31.25MHz +# If enabled, SPI runs at 62.5MHz, which is 25% higher than 50Mhz which is the maximum according to the CYW43439 datasheet. +overclock = [] + [dependencies] cyw43 = { path = "../" } embassy-rp = { version = "0.1.0", features = ["unstable-traits", "nightly", "unstable-pac", "time-driver"] } diff --git a/cyw43-pio/src/lib.rs b/cyw43-pio/src/lib.rs index 2c17b151..dca30c74 100644 --- a/cyw43-pio/src/lib.rs +++ b/cyw43-pio/src/lib.rs @@ -40,24 +40,46 @@ where DIO: PioPin, CLK: PioPin, { + #[cfg(feature = "overclock")] let program = pio_asm!( ".side_set 1" ".wrap_target" // write out x-1 bits - "lp:", + "lp:" "out pins, 1 side 0" "jmp x-- lp side 1" // switch directions "set pindirs, 0 side 0" - // these nops seem to be necessary for fast clkdiv - //"nop side 1" - //"nop side 0" - "nop side 1" + "nop side 1" // necessary for clkdiv=1. + "nop side 0" // read in y-1 bits "lp2:" - "in pins, 1 side 0" - "jmp y-- lp2 side 1" + "in pins, 1 side 1" + "jmp y-- lp2 side 0" + + // wait for event and irq host + "wait 1 pin 0 side 0" + "irq 0 side 0" + + ".wrap" + ); + #[cfg(not(feature = "overclock"))] + let program = pio_asm!( + ".side_set 1" + + ".wrap_target" + // write out x-1 bits + "lp:" + "out pins, 1 side 0" + "jmp x-- lp side 1" + // switch directions + "set pindirs, 0 side 0" + "nop side 0" + // read in y-1 bits + "lp2:" + "in pins, 1 side 1" + "jmp y-- lp2 side 0" // wait for event and irq host "wait 1 pin 0 side 0" @@ -72,8 +94,8 @@ where pin_io.set_pull(Pull::None); pin_io.set_schmitt(true); pin_io.set_input_sync_bypass(true); - //pin_io.set_drive_strength(Drive::_12mA); - //pin_io.set_slew_rate(SlewRate::Fast); + pin_io.set_drive_strength(Drive::_12mA); + pin_io.set_slew_rate(SlewRate::Fast); let mut pin_clk = common.make_pio_pin(clk); pin_clk.set_drive_strength(Drive::_12mA); @@ -91,27 +113,24 @@ where cfg.shift_in.auto_fill = true; //cfg.shift_in.threshold = 32; - // theoretical maximum according to data sheet, 100Mhz Pio => 50Mhz SPI Freq - // seems to cause random corruption, probably due to jitter due to the fractional divider. - // cfg.clock_divider = FixedU32::from_bits(0x0140); + #[cfg(feature = "overclock")] + { + // 125mhz Pio => 62.5Mhz SPI Freq. 25% higher than theoretical maximum according to + // data sheet, but seems to work fine. + cfg.clock_divider = FixedU32::from_bits(0x0100); + } - // same speed as pico-sdk, 62.5Mhz - cfg.clock_divider = FixedU32::from_bits(0x0200); - - // 32 Mhz - // cfg.clock_divider = FixedU32::from_bits(0x03E8); - - // 16 Mhz - // cfg.clock_divider = FixedU32::from_bits(0x07d0); - - // 8Mhz - // cfg.clock_divider = FixedU32::from_bits(0x0a_00); - - // 1Mhz - // cfg.clock_divider = FixedU32::from_bits(0x7d_00); - - // slowest possible - // cfg.clock_divider = FixedU32::from_bits(0xffff_00); + #[cfg(not(feature = "overclock"))] + { + // same speed as pico-sdk, 62.5Mhz + // This is actually the fastest we can go without overclocking. + // According to data sheet, the theoretical maximum is 100Mhz Pio => 50Mhz SPI Freq. + // However, the PIO uses a fractional divider, which works by introducing jitter when + // the divider is not an integer. It does some clocks at 125mhz and others at 62.5mhz + // so that it averages out to the desired frequency of 100mhz. The 125mhz clock cycles + // violate the maximum from the data sheet. + cfg.clock_divider = FixedU32::from_bits(0x0200); + } sm.set_config(&cfg); diff --git a/examples/rpi-pico-w/Cargo.toml b/examples/rpi-pico-w/Cargo.toml index d972bf5a..525e934f 100644 --- a/examples/rpi-pico-w/Cargo.toml +++ b/examples/rpi-pico-w/Cargo.toml @@ -6,7 +6,7 @@ edition = "2021" [dependencies] cyw43 = { path = "../../", features = ["defmt", "firmware-logs"] } -cyw43-pio = { path = "../../cyw43-pio", features = ["defmt"] } +cyw43-pio = { path = "../../cyw43-pio", features = ["defmt", "overclock"] } embassy-executor = { version = "0.2.0", features = ["defmt", "integrated-timers", "executor-thread", "arch-cortex-m"] } embassy-time = { version = "0.1.0", features = ["defmt", "defmt-timestamp-uptime"] } embassy-rp = { version = "0.1.0", features = ["defmt", "unstable-traits", "nightly", "unstable-pac", "time-driver"] } @@ -48,7 +48,7 @@ debug = 1 debug-assertions = false incremental = false lto = 'fat' -opt-level = 'z' +opt-level = 's' overflow-checks = false # do not optimize proc-macro crates = faster builds from scratch From a19f8c32ffe085fdb68d349599dac360721b7786 Mon Sep 17 00:00:00 2001 From: Olivier Monnom <7986118+papyDoctor@users.noreply.github.com> Date: Wed, 24 May 2023 09:22:05 +0200 Subject: [PATCH 136/178] Update examples in README.md --- README.md | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index fe8d5d93..defea489 100644 --- a/README.md +++ b/README.md @@ -21,27 +21,28 @@ TODO: - Setting a custom MAC address. - Bus sleep (unclear what the benefit is. Is it needed for IRQs? or is it just power consumption optimization?) -## Running the example +## Running the examples - `cargo install probe-rs-cli` - `cd examples/rpi-pico-w` +### Example 1: Scan the wifi stations +- `cargo run --release --bin wifi_scan` +### Example 2: Create an access point (IP and credentials in the code) +- `cargo run --release --bin tcp_server_ap` +### Example 3: Connect to an existing network and create a server - `WIFI_NETWORK=MyWifiNetwork WIFI_PASSWORD=MyWifiPassword cargo run --release` After a few seconds, you should see that DHCP picks up an IP address like this - ``` 11.944489 DEBUG Acquired IP configuration: 11.944517 DEBUG IP address: 192.168.0.250/24 11.944620 DEBUG Default gateway: 192.168.0.33 11.944722 DEBUG DNS server 0: 192.168.0.33 ``` - -The example implements a TCP echo server on port 1234. You can try connecting to it with: - +This example implements a TCP echo server on port 1234. You can try connecting to it with: ``` nc 192.168.0.250 1234 ``` - Send it some data, you should see it echoed back and printed in the firmware's logs. ## License From abbaaeee378f41ddf9f1a3c40777be3572680484 Mon Sep 17 00:00:00 2001 From: goueslati Date: Thu, 25 May 2023 16:39:43 +0100 Subject: [PATCH 137/178] stm32/ipcc: support for MAC 802.15.4 --- embassy-stm32/Cargo.toml | 10 ++ embassy-stm32/src/tl_mbox/ble.rs | 23 +++- embassy-stm32/src/tl_mbox/channels.rs | 146 ++++++++++++++-------- embassy-stm32/src/tl_mbox/mac_802_15_4.rs | 82 ++++++++++++ embassy-stm32/src/tl_mbox/mm.rs | 12 +- embassy-stm32/src/tl_mbox/mod.rs | 115 ++++++++++++----- embassy-stm32/src/tl_mbox/shci.rs | 4 +- embassy-stm32/src/tl_mbox/sys.rs | 16 ++- 8 files changed, 306 insertions(+), 102 deletions(-) create mode 100644 embassy-stm32/src/tl_mbox/mac_802_15_4.rs diff --git a/embassy-stm32/Cargo.toml b/embassy-stm32/Cargo.toml index 21adb5dd..c2b0a3ec 100644 --- a/embassy-stm32/Cargo.toml +++ b/embassy-stm32/Cargo.toml @@ -110,6 +110,16 @@ unstable-pac = [] # Implement embedded-hal-async traits if `nightly` is set as well. unstable-traits = ["embedded-hal-1", "dep:embedded-hal-nb"] +# stm32wb specific +# support for wireless stacks +ble = [] +thread = [] +lld-tests = [] +ble-lld = [] +mac-802_15_4 = [] +zigbee = [] +traces = [] + # Chip-selection features stm32c011d6 = [ "stm32-metapac/stm32c011d6" ] stm32c011f4 = [ "stm32-metapac/stm32c011f4" ] diff --git a/embassy-stm32/src/tl_mbox/ble.rs b/embassy-stm32/src/tl_mbox/ble.rs index d8bf14d4..21688ff4 100644 --- a/embassy-stm32/src/tl_mbox/ble.rs +++ b/embassy-stm32/src/tl_mbox/ble.rs @@ -16,7 +16,7 @@ use crate::tl_mbox::cmd::CmdPacket; pub struct Ble; impl Ble { - pub(crate) fn new(ipcc: &mut Ipcc) -> Self { + pub(crate) fn init(ipcc: &mut Ipcc) -> Self { unsafe { LinkedListNode::init_head(EVT_QUEUE.as_mut_ptr()); @@ -28,7 +28,7 @@ impl Ble { }); } - ipcc.c1_set_rx_channel(channels::cpu2::IPCC_BLE_EVENT_CHANNEL, true); + ipcc.c1_set_rx_channel(channels::Cpu2Channel::BleEvent.into(), true); Ble } @@ -48,7 +48,11 @@ impl Ble { } } - ipcc.c1_clear_flag_channel(channels::cpu2::IPCC_BLE_EVENT_CHANNEL); + ipcc.c1_clear_flag_channel(channels::Cpu2Channel::BleEvent.into()); + } + + pub(crate) fn acl_data_evt_handler(ipcc: &mut Ipcc) { + ipcc.c1_set_tx_channel(channels::Cpu1Channel::HciAclData.into(), false); } pub(crate) fn send_cmd(ipcc: &mut Ipcc, buf: &[u8]) { @@ -63,6 +67,17 @@ impl Ble { cmd_packet.cmd_serial.ty = TlPacketType::BleCmd as u8; } - ipcc.c1_set_flag_channel(channels::cpu1::IPCC_BLE_CMD_CHANNEL); + ipcc.c1_set_flag_channel(channels::Cpu1Channel::BleCmd.into()); + } + + pub(crate) fn send_acl_data(ipcc: &mut Ipcc) { + unsafe { + (*(*TL_REF_TABLE.assume_init().ble_table).phci_acl_data_buffer) + .acl_data_serial + .ty = TlPacketType::AclData as u8; + } + + ipcc.c1_set_flag_channel(channels::Cpu1Channel::HciAclData.into()); + ipcc.c1_set_tx_channel(channels::Cpu1Channel::HciAclData.into(), true); } } diff --git a/embassy-stm32/src/tl_mbox/channels.rs b/embassy-stm32/src/tl_mbox/channels.rs index aaa6ce17..94e97230 100644 --- a/embassy-stm32/src/tl_mbox/channels.rs +++ b/embassy-stm32/src/tl_mbox/channels.rs @@ -49,56 +49,104 @@ //! | | //! -pub mod cpu1 { - use crate::ipcc::IpccChannel; +use crate::ipcc::IpccChannel; - // Not used currently but reserved - pub const IPCC_BLE_CMD_CHANNEL: IpccChannel = IpccChannel::Channel1; - // Not used currently but reserved - pub const IPCC_SYSTEM_CMD_RSP_CHANNEL: IpccChannel = IpccChannel::Channel2; - #[allow(dead_code)] // Not used currently but reserved - pub const IPCC_THREAD_OT_CMD_RSP_CHANNEL: IpccChannel = IpccChannel::Channel3; - #[allow(dead_code)] // Not used currently but reserved - pub const IPCC_ZIGBEE_CMD_APPLI_CHANNEL: IpccChannel = IpccChannel::Channel3; - #[allow(dead_code)] // Not used currently but reserved - pub const IPCC_MAC_802_15_4_CMD_RSP_CHANNEL: IpccChannel = IpccChannel::Channel3; - // Not used currently but reserved - pub const IPCC_MM_RELEASE_BUFFER_CHANNEL: IpccChannel = IpccChannel::Channel4; - #[allow(dead_code)] // Not used currently but reserved - pub const IPCC_THREAD_CLI_CMD_CHANNEL: IpccChannel = IpccChannel::Channel5; - #[allow(dead_code)] // Not used currently but reserved - pub const IPCC_LLDTESTS_CLI_CMD_CHANNEL: IpccChannel = IpccChannel::Channel5; - #[allow(dead_code)] // Not used currently but reserved - pub const IPCC_BLE_LLD_CMD_CHANNEL: IpccChannel = IpccChannel::Channel5; - #[allow(dead_code)] // Not used currently but reserved - pub const IPCC_HCI_ACL_DATA_CHANNEL: IpccChannel = IpccChannel::Channel6; +pub enum Cpu1Channel { + BleCmd, + SystemCmdRsp, + #[cfg(feature = "thread")] + ThreadOtCmdRsp, + #[cfg(feature = "zigbee")] + ZigbeeCmdAppli, + MmReleaseBuffer, + #[cfg(feature = "mac-802_15_4")] + Mac802_15_4cmdRsp, + #[cfg(feature = "thread")] + ThreadCliCmd, + #[cfg(feature = "lld-tests")] + LldTestsCliCmd, + #[cfg(feature = "ble-lld")] + BleLldCmd, + HciAclData, } -pub mod cpu2 { - use crate::ipcc::IpccChannel; - - pub const IPCC_BLE_EVENT_CHANNEL: IpccChannel = IpccChannel::Channel1; - pub const IPCC_SYSTEM_EVENT_CHANNEL: IpccChannel = IpccChannel::Channel2; - #[allow(dead_code)] // Not used currently but reserved - pub const IPCC_THREAD_NOTIFICATION_ACK_CHANNEL: IpccChannel = IpccChannel::Channel3; - #[allow(dead_code)] // Not used currently but reserved - pub const IPCC_ZIGBEE_APPLI_NOTIF_ACK_CHANNEL: IpccChannel = IpccChannel::Channel3; - #[allow(dead_code)] // Not used currently but reserved - pub const IPCC_MAC_802_15_4_NOTIFICATION_ACK_CHANNEL: IpccChannel = IpccChannel::Channel3; - #[allow(dead_code)] // Not used currently but reserved - pub const IPCC_LDDTESTS_M0_CMD_CHANNEL: IpccChannel = IpccChannel::Channel3; - #[allow(dead_code)] // Not used currently but reserved - pub const IPCC_BLE_LLD_M0_CMD_CHANNEL: IpccChannel = IpccChannel::Channel3; - #[allow(dead_code)] // Not used currently but reserved - pub const IPCC_TRACES_CHANNEL: IpccChannel = IpccChannel::Channel4; - #[allow(dead_code)] // Not used currently but reserved - pub const IPCC_THREAD_CLI_NOTIFICATION_ACK_CHANNEL: IpccChannel = IpccChannel::Channel5; - #[allow(dead_code)] // Not used currently but reserved - pub const IPCC_LLDTESTS_CLI_RSP_CHANNEL: IpccChannel = IpccChannel::Channel5; - #[allow(dead_code)] // Not used currently but reserved - pub const IPCC_BLE_LLD_CLI_RSP_CHANNEL: IpccChannel = IpccChannel::Channel5; - #[allow(dead_code)] // Not used currently but reserved - pub const IPCC_BLE_LLD_RSP_CHANNEL: IpccChannel = IpccChannel::Channel5; - #[allow(dead_code)] // Not used currently but reserved - pub const IPCC_ZIGBEE_M0_REQUEST_CHANNEL: IpccChannel = IpccChannel::Channel5; +impl From for IpccChannel { + fn from(value: Cpu1Channel) -> Self { + match value { + Cpu1Channel::BleCmd => IpccChannel::Channel1, + Cpu1Channel::SystemCmdRsp => IpccChannel::Channel2, + #[cfg(feature = "thread")] + Cpu1Channel::ThreadOtCmdRsp => IpccChannel::Channel3, + #[cfg(feature = "zigbee")] + Cpu1Channel::ZigbeeCmdAppli => IpccChannel::Channel3, + #[cfg(feature = "mac-802_15_4")] + Cpu1Channel::Mac802_15_4cmdRsp => IpccChannel::Channel3, + Cpu1Channel::MmReleaseBuffer => IpccChannel::Channel4, + #[cfg(feature = "thread")] + Cpu1Channel::ThreadCliCmd => IpccChannel::Channel5, + #[cfg(feature = "lld-tests")] + Cpu1Channel::LldTestsCliCmd => IpccChannel::Channel5, + #[cfg(feature = "ble-lld")] + Cpu1Channel::BleLldCmd => IpccChannel::Channel5, + Cpu1Channel::HciAclData => IpccChannel::Channel6, + } + } +} + +pub enum Cpu2Channel { + BleEvent, + SystemEvent, + #[cfg(feature = "thread")] + ThreadNotifAck, + #[cfg(feature = "zigbee")] + ZigbeeAppliNotifAck, + #[cfg(feature = "mac-802_15_4")] + Mac802_15_4NotifAck, + #[cfg(feature = "lld-tests")] + LldTestsM0Cmd, + #[cfg(feature = "ble-lld")] + BleLldM0Cmd, + #[cfg(feature = "traces")] + Traces, + #[cfg(feature = "thread")] + ThreadCliNotifAck, + #[cfg(feature = "lld-tests")] + LldTestsCliRsp, + #[cfg(feature = "ble-lld")] + BleLldCliRsp, + #[cfg(feature = "ble-lld")] + BleLldRsp, + #[cfg(feature = "zigbee")] + ZigbeeM0Request, +} + +impl From for IpccChannel { + fn from(value: Cpu2Channel) -> Self { + match value { + Cpu2Channel::BleEvent => IpccChannel::Channel1, + Cpu2Channel::SystemEvent => IpccChannel::Channel2, + #[cfg(feature = "thread")] + Cpu2Channel::ThreadNotifAck => IpccChannel::Channel3, + #[cfg(feature = "zigbee")] + Cpu2Channel::ZigbeeAppliNotifAck => IpccChannel::Channel3, + #[cfg(feature = "mac-802_15_4")] + Cpu2Channel::Mac802_15_4NotifAck => IpccChannel::Channel3, + #[cfg(feature = "lld-tests")] + Cpu2Channel::LldTestsM0Cmd => IpccChannel::Channel3, + #[cfg(feature = "ble-lld")] + Cpu2Channel::BleLldM0Cmd => IpccChannel::Channel3, + #[cfg(feature = "traces")] + Cpu2Channel::Traces => IpccChannel::Channel4, + #[cfg(feature = "thread")] + Cpu2Channel::ThreadCliNotifAck => IpccChannel::Channel5, + #[cfg(feature = "lld-tests")] + Cpu2Channel::LldTestsCliRsp => IpccChannel::Channel5, + #[cfg(feature = "ble-lld")] + Cpu2Channel::BleLldCliRsp => IpccChannel::Channel5, + #[cfg(feature = "ble-lld")] + Cpu2Channel::BleLldRsp => IpccChannel::Channel5, + #[cfg(feature = "zigbee")] + Cpu2Channel::ZigbeeM0Request => IpccChannel::Channel5, + } + } } diff --git a/embassy-stm32/src/tl_mbox/mac_802_15_4.rs b/embassy-stm32/src/tl_mbox/mac_802_15_4.rs new file mode 100644 index 00000000..19f95113 --- /dev/null +++ b/embassy-stm32/src/tl_mbox/mac_802_15_4.rs @@ -0,0 +1,82 @@ +use core::mem::MaybeUninit; + +use embassy_futures::block_on; + +use super::cmd::{CmdPacket, CmdSerial}; +use super::consts::TlPacketType; +use super::evt::{EvtBox, EvtPacket}; +use super::unsafe_linked_list::LinkedListNode; +use super::{ + channels, Mac802_15_4Table, EVT_QUEUE, MAC_802_15_4_CMD_BUFFER, MAC_802_15_4_NOTIF_RSP_EVT_BUFFER, TL_CHANNEL, + TL_MAC_802_15_4_TABLE, TL_REF_TABLE, +}; +use crate::ipcc::Ipcc; + +pub struct Mac802_15_4; + +impl Mac802_15_4 { + pub(crate) fn init(ipcc: &mut Ipcc) -> Self { + unsafe { + LinkedListNode::init_head(EVT_QUEUE.as_mut_ptr()); + + TL_MAC_802_15_4_TABLE = MaybeUninit::new(Mac802_15_4Table { + pcmd_rsp_buffer: MAC_802_15_4_CMD_BUFFER.as_mut_ptr().cast(), + pnotack_buffer: MAC_802_15_4_NOTIF_RSP_EVT_BUFFER.as_mut_ptr().cast(), + evt_queue: EVT_QUEUE.as_ptr().cast(), + }); + } + + ipcc.c1_set_rx_channel(channels::Cpu2Channel::Mac802_15_4NotifAck.into(), true); + + Self + } + + pub(crate) fn notif_evt_handler(ipcc: &mut Ipcc) { + unsafe { + let notif_buffer: *mut EvtPacket = (*TL_REF_TABLE.assume_init().mac_802_15_4_table).pnotack_buffer.cast(); + let event = EvtBox::new(notif_buffer); + + block_on(TL_CHANNEL.send(event)); + } + + ipcc.c1_set_rx_channel(channels::Cpu2Channel::Mac802_15_4NotifAck.into(), false); + } + + pub(crate) fn cmd_evt_handler(ipcc: &mut Ipcc) { + unsafe { + let _notif_buffer = (*TL_REF_TABLE.assume_init().mac_802_15_4_table).pcmd_rsp_buffer; + + // NOTE: ST's HAL does nothing with this buffer, ?????? + } + + ipcc.c1_set_tx_channel(channels::Cpu1Channel::Mac802_15_4cmdRsp.into(), false); + } + + pub(crate) fn send_cmd(ipcc: &mut Ipcc, buf: &[u8]) { + unsafe { + let pcmd_buffer: *mut CmdPacket = (*TL_REF_TABLE.assume_init().mac_802_15_4_table).pcmd_rsp_buffer.cast(); + let pcmd_serial: *mut CmdSerial = &mut (*pcmd_buffer).cmd_serial; + let pcmd_serial_buf: *mut u8 = pcmd_serial.cast(); + + core::ptr::copy(buf.as_ptr(), pcmd_serial_buf, buf.len()); + + let cmd_packet: &mut CmdPacket = + &mut *(*TL_REF_TABLE.assume_init().mac_802_15_4_table).pcmd_rsp_buffer.cast(); + cmd_packet.cmd_serial.ty = TlPacketType::OtCmd as u8; + } + + ipcc.c1_set_flag_channel(channels::Cpu1Channel::Mac802_15_4cmdRsp.into()); + ipcc.c1_set_tx_channel(channels::Cpu1Channel::Mac802_15_4cmdRsp.into(), true); + } + + pub(crate) fn send_ack(ipcc: &mut Ipcc) { + // TODO + unsafe { + let packet: &mut CmdPacket = &mut *(*TL_REF_TABLE.assume_init().mac_802_15_4_table).pnotack_buffer.cast(); + packet.cmd_serial.ty = TlPacketType::OtAck as u8; + } + + ipcc.c1_clear_flag_channel(channels::Cpu2Channel::Mac802_15_4NotifAck.into()); + ipcc.c1_set_rx_channel(channels::Cpu2Channel::Mac802_15_4NotifAck.into(), true); + } +} diff --git a/embassy-stm32/src/tl_mbox/mm.rs b/embassy-stm32/src/tl_mbox/mm.rs index f99ffa39..5dec397e 100644 --- a/embassy-stm32/src/tl_mbox/mm.rs +++ b/embassy-stm32/src/tl_mbox/mm.rs @@ -11,7 +11,7 @@ use crate::ipcc::Ipcc; pub struct MemoryManager; impl MemoryManager { - pub fn new() -> Self { + pub fn init() -> Self { unsafe { LinkedListNode::init_head(FREE_BUFF_QUEUE.as_mut_ptr()); LinkedListNode::init_head(LOCAL_FREE_BUF_QUEUE.as_mut_ptr()); @@ -31,9 +31,9 @@ impl MemoryManager { } pub fn evt_handler(ipcc: &mut Ipcc) { - ipcc.c1_set_tx_channel(channels::cpu1::IPCC_MM_RELEASE_BUFFER_CHANNEL, false); + ipcc.c1_set_tx_channel(channels::Cpu1Channel::MmReleaseBuffer.into(), false); Self::send_free_buf(); - ipcc.c1_set_flag_channel(channels::cpu1::IPCC_MM_RELEASE_BUFFER_CHANNEL); + ipcc.c1_set_flag_channel(channels::Cpu1Channel::MmReleaseBuffer.into()); } pub fn evt_drop(evt: *mut EvtPacket, ipcc: &mut Ipcc) { @@ -43,14 +43,14 @@ impl MemoryManager { LinkedListNode::remove_tail(LOCAL_FREE_BUF_QUEUE.as_mut_ptr(), list_node); } - let channel_is_busy = ipcc.c1_is_active_flag(channels::cpu1::IPCC_MM_RELEASE_BUFFER_CHANNEL); + let channel_is_busy = ipcc.c1_is_active_flag(channels::Cpu1Channel::MmReleaseBuffer.into()); // postpone event buffer freeing to IPCC interrupt handler if channel_is_busy { - ipcc.c1_set_tx_channel(channels::cpu1::IPCC_MM_RELEASE_BUFFER_CHANNEL, true); + ipcc.c1_set_tx_channel(channels::Cpu1Channel::MmReleaseBuffer.into(), true); } else { Self::send_free_buf(); - ipcc.c1_set_flag_channel(channels::cpu1::IPCC_MM_RELEASE_BUFFER_CHANNEL); + ipcc.c1_set_flag_channel(channels::Cpu1Channel::MmReleaseBuffer.into()); } } diff --git a/embassy-stm32/src/tl_mbox/mod.rs b/embassy-stm32/src/tl_mbox/mod.rs index dc6104cc..b2d3a27e 100644 --- a/embassy-stm32/src/tl_mbox/mod.rs +++ b/embassy-stm32/src/tl_mbox/mod.rs @@ -4,9 +4,12 @@ use bit_field::BitField; use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; use embassy_sync::channel::Channel; +#[cfg(feature = "ble")] use self::ble::Ble; use self::cmd::{AclDataPacket, CmdPacket}; use self::evt::{CsEvt, EvtBox}; +#[cfg(feature = "mac-802_15_4")] +use self::mac_802_15_4::Mac802_15_4; use self::mm::MemoryManager; use self::shci::{shci_ble_init, ShciBleInitCmdParam}; use self::sys::Sys; @@ -14,7 +17,6 @@ use self::unsafe_linked_list::LinkedListNode; use crate::interrupt; use crate::ipcc::Ipcc; -mod ble; mod channels; mod cmd; mod consts; @@ -24,6 +26,11 @@ mod shci; mod sys; mod unsafe_linked_list; +#[cfg(feature = "ble")] +mod ble; +#[cfg(feature = "mac-802_15_4")] +mod mac_802_15_4; + pub type PacketHeader = LinkedListNode; const TL_PACKET_HEADER_SIZE: usize = core::mem::size_of::(); @@ -194,8 +201,8 @@ struct TracesTable { #[repr(C, packed)] struct Mac802_15_4Table { - pcmd_rsp_buffer: *const u8, - pnotack_buffer: *const u8, + pcmd_rsp_buffer: *mut u8, + pnotack_buffer: *mut u8, evt_queue: *const u8, } @@ -215,6 +222,7 @@ pub struct RefTable { ble_lld_table: *const BleLldTable, } +// -------------------- reference table -------------------- #[link_section = "TL_REF_TABLE"] pub static mut TL_REF_TABLE: MaybeUninit = MaybeUninit::uninit(); @@ -248,38 +256,50 @@ static mut TL_MAC_802_15_4_TABLE: MaybeUninit = MaybeUninit::u #[link_section = "MB_MEM1"] static mut TL_ZIGBEE_TABLE: MaybeUninit = MaybeUninit::uninit(); -#[allow(dead_code)] // Not used currently but reserved +// -------------------- tables -------------------- #[link_section = "MB_MEM1"] static mut FREE_BUFF_QUEUE: MaybeUninit = MaybeUninit::uninit(); -// not in shared RAM -static mut LOCAL_FREE_BUF_QUEUE: MaybeUninit = MaybeUninit::uninit(); - #[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"] +#[link_section = "MB_MEM1"] static mut EVT_QUEUE: MaybeUninit = MaybeUninit::uninit(); #[link_section = "MB_MEM2"] static mut SYSTEM_EVT_QUEUE: MaybeUninit = MaybeUninit::uninit(); -#[link_section = "MB_MEM2"] -static mut SYS_CMD_BUF: MaybeUninit = MaybeUninit::uninit(); +// not in shared RAM +static mut LOCAL_FREE_BUF_QUEUE: MaybeUninit = MaybeUninit::uninit(); +// -------------------- app tables -------------------- #[link_section = "MB_MEM2"] static mut EVT_POOL: MaybeUninit<[u8; POOL_SIZE]> = MaybeUninit::uninit(); +#[link_section = "MB_MEM2"] +static mut SYS_CMD_BUF: MaybeUninit = 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(); +#[cfg(feature = "mac-802_15_4")] +#[link_section = "MB_MEM2"] +static mut MAC_802_15_4_CMD_BUFFER: MaybeUninit = MaybeUninit::uninit(); + +#[cfg(feature = "mac-802_15_4")] +#[link_section = "MB_MEM2"] +static mut MAC_802_15_4_NOTIF_RSP_EVT_BUFFER: MaybeUninit<[u8; TL_PACKET_HEADER_SIZE + TL_EVT_HEADER_SIZE + 255]> = + MaybeUninit::uninit(); + +#[cfg(feature = "ble")] #[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"] +#[cfg(feature = "ble")] +#[link_section = "MB_MEM1"] static mut BLE_CMD_BUFFER: MaybeUninit = MaybeUninit::uninit(); #[link_section = "MB_MEM2"] @@ -289,10 +309,14 @@ static mut HCI_ACL_DATA_BUFFER: MaybeUninit<[u8; TL_PACKET_HEADER_SIZE + 5 + 251 // TODO: get a better size, this is a placeholder pub(crate) static TL_CHANNEL: Channel = Channel::new(); -pub struct TlMbox { - _sys: Sys, - _ble: Ble, - _mm: MemoryManager, +pub struct TlMbox; + +pub enum MailboxTarget { + Sys, + #[cfg(feature = "ble")] + Ble, + #[cfg(feature = "mac-802_15_4")] + Mac802_15_4, } impl TlMbox { @@ -338,9 +362,14 @@ impl TlMbox { ipcc.init(); - let _sys = Sys::new(ipcc); - let _ble = Ble::new(ipcc); - let _mm = MemoryManager::new(); + Sys::init(ipcc); + MemoryManager::init(); + + #[cfg(feature = "ble")] + Ble::init(ipcc); + + #[cfg(feature = "mac-802_15_4")] + Mac802_15_4::init(ipcc); // rx_irq.disable(); // tx_irq.disable(); @@ -360,7 +389,7 @@ impl TlMbox { // rx_irq.enable(); // tx_irq.enable(); - TlMbox { _sys, _ble, _mm } + Self } pub fn wireless_fw_info(&self) -> Option { @@ -374,17 +403,30 @@ impl TlMbox { } } + #[cfg(feature = "ble")] pub fn shci_ble_init(&self, ipcc: &mut Ipcc, param: ShciBleInitCmdParam) { shci_ble_init(ipcc, param); } - pub fn send_ble_cmd(&self, ipcc: &mut Ipcc, buf: &[u8]) { - ble::Ble::send_cmd(ipcc, buf); + pub fn send_cmd(&self, ipcc: &mut Ipcc, buf: &[u8], target: MailboxTarget) { + match target { + MailboxTarget::Sys => Sys::send_cmd(ipcc, buf), + #[cfg(feature = "ble")] + MailboxTarget::Ble => Ble::send_cmd(ipcc, buf), + #[cfg(feature = "mac-802_15_4")] + MailboxTarget::Mac802_15_4 => Mac802_15_4::send_cmd(ipcc, buf), + } } - // pub fn send_sys_cmd(&self, ipcc: &mut Ipcc, buf: &[u8]) { - // sys::Sys::send_cmd(ipcc, buf); - // } + pub fn send_ack(&self, ipcc: &mut Ipcc, target: MailboxTarget) { + match target { + #[cfg(feature = "ble")] + MailboxTarget::Ble => Ble::send_acl_data(ipcc), + #[cfg(feature = "mac-802_15_4")] + MailboxTarget::Mac802_15_4 => Mac802_15_4::send_ack(ipcc), + MailboxTarget::Sys => { /* does nothing */ } + } + } pub async fn read(&self) -> EvtBox { TL_CHANNEL.recv().await @@ -392,10 +434,14 @@ impl TlMbox { #[allow(dead_code)] fn interrupt_ipcc_rx_handler(ipcc: &mut Ipcc) { - if ipcc.is_rx_pending(channels::cpu2::IPCC_SYSTEM_EVENT_CHANNEL) { - sys::Sys::evt_handler(ipcc); - } else if ipcc.is_rx_pending(channels::cpu2::IPCC_BLE_EVENT_CHANNEL) { - ble::Ble::evt_handler(ipcc); + if ipcc.is_rx_pending(channels::Cpu2Channel::SystemEvent.into()) { + Sys::evt_handler(ipcc); + } else if cfg!(feature = "ble") && ipcc.is_rx_pending(channels::Cpu2Channel::BleEvent.into()) { + Ble::evt_handler(ipcc); + } else if cfg!(feature = "mac-802_15_4") + && ipcc.is_rx_pending(channels::Cpu2Channel::Mac802_15_4NotifAck.into()) + { + Mac802_15_4::notif_evt_handler(ipcc); } else { todo!() } @@ -403,11 +449,16 @@ impl TlMbox { #[allow(dead_code)] fn interrupt_ipcc_tx_handler(ipcc: &mut Ipcc) { - if ipcc.is_tx_pending(channels::cpu1::IPCC_SYSTEM_CMD_RSP_CHANNEL) { + if ipcc.is_tx_pending(channels::Cpu1Channel::SystemCmdRsp.into()) { // TODO: handle this case - let _ = sys::Sys::cmd_evt_handler(ipcc); - } else if ipcc.is_tx_pending(channels::cpu1::IPCC_MM_RELEASE_BUFFER_CHANNEL) { - mm::MemoryManager::evt_handler(ipcc); + let _ = Sys::cmd_evt_handler(ipcc); + } else if ipcc.is_tx_pending(channels::Cpu1Channel::MmReleaseBuffer.into()) { + MemoryManager::evt_handler(ipcc); + } else if cfg!(feature = "ble") && ipcc.is_tx_pending(channels::Cpu1Channel::HciAclData.into()) { + Ble::acl_data_evt_handler(ipcc); + } else if cfg!(feature = "mac-802_15_4") && ipcc.is_tx_pending(channels::Cpu1Channel::Mac802_15_4cmdRsp.into()) + { + Mac802_15_4::cmd_evt_handler(ipcc) } else { todo!() } diff --git a/embassy-stm32/src/tl_mbox/shci.rs b/embassy-stm32/src/tl_mbox/shci.rs index 61fd9e4a..53f0882b 100644 --- a/embassy-stm32/src/tl_mbox/shci.rs +++ b/embassy-stm32/src/tl_mbox/shci.rs @@ -95,7 +95,7 @@ pub fn shci_ble_init(ipcc: &mut Ipcc, param: ShciBleInitCmdParam) { cmd_buf.cmd_serial.ty = TlPacketType::SysCmd as u8; - ipcc.c1_set_flag_channel(channels::cpu1::IPCC_SYSTEM_CMD_RSP_CHANNEL); - ipcc.c1_set_tx_channel(channels::cpu1::IPCC_SYSTEM_CMD_RSP_CHANNEL, true); + ipcc.c1_set_flag_channel(channels::Cpu1Channel::SystemCmdRsp.into()); + ipcc.c1_set_tx_channel(channels::Cpu1Channel::SystemCmdRsp.into(), true); } } diff --git a/embassy-stm32/src/tl_mbox/sys.rs b/embassy-stm32/src/tl_mbox/sys.rs index 31ebde72..1dc43bfe 100644 --- a/embassy-stm32/src/tl_mbox/sys.rs +++ b/embassy-stm32/src/tl_mbox/sys.rs @@ -12,7 +12,7 @@ use crate::ipcc::Ipcc; pub struct Sys; impl Sys { - pub(crate) fn new(ipcc: &mut Ipcc) -> Self { + pub(crate) fn init(ipcc: &mut Ipcc) -> Self { unsafe { LinkedListNode::init_head(SYSTEM_EVT_QUEUE.as_mut_ptr()); @@ -22,7 +22,7 @@ impl Sys { }); } - ipcc.c1_set_rx_channel(channels::cpu2::IPCC_SYSTEM_EVENT_CHANNEL, true); + ipcc.c1_set_rx_channel(channels::Cpu2Channel::SystemEvent.into(), true); Sys } @@ -43,11 +43,11 @@ impl Sys { } } - ipcc.c1_clear_flag_channel(channels::cpu2::IPCC_SYSTEM_EVENT_CHANNEL); + ipcc.c1_clear_flag_channel(channels::Cpu2Channel::SystemEvent.into()); } pub(crate) fn cmd_evt_handler(ipcc: &mut Ipcc) -> CcEvt { - ipcc.c1_set_tx_channel(channels::cpu1::IPCC_SYSTEM_CMD_RSP_CHANNEL, false); + ipcc.c1_set_tx_channel(channels::Cpu1Channel::SystemCmdRsp.into(), false); // ST's command response data structure is really convoluted. // @@ -67,12 +67,10 @@ impl Sys { } } - #[allow(dead_code)] pub(crate) fn send_cmd(ipcc: &mut Ipcc, buf: &[u8]) { unsafe { - // TODO: check this let cmd_buffer = &mut *(*TL_REF_TABLE.assume_init().sys_table).pcmd_buffer; - let cmd_serial: *mut CmdSerial = &mut (*cmd_buffer).cmd_serial; + let cmd_serial: *mut CmdSerial = &mut cmd_buffer.cmd_serial; let cmd_serial_buf = cmd_serial.cast(); core::ptr::copy(buf.as_ptr(), cmd_serial_buf, buf.len()); @@ -80,8 +78,8 @@ impl Sys { let cmd_packet = &mut *(*TL_REF_TABLE.assume_init().sys_table).pcmd_buffer; cmd_packet.cmd_serial.ty = TlPacketType::SysCmd as u8; - ipcc.c1_set_flag_channel(channels::cpu1::IPCC_SYSTEM_CMD_RSP_CHANNEL); - ipcc.c1_set_tx_channel(channels::cpu1::IPCC_SYSTEM_CMD_RSP_CHANNEL, true); + ipcc.c1_set_flag_channel(channels::Cpu1Channel::SystemCmdRsp.into()); + ipcc.c1_set_tx_channel(channels::Cpu1Channel::SystemCmdRsp.into(), true); } } } From 2ccf9f3abd6c8ccde0e56f97cbe65e5fb4bc32ce Mon Sep 17 00:00:00 2001 From: goueslati Date: Fri, 26 May 2023 09:56:55 +0100 Subject: [PATCH 138/178] stm32/ipcc: static methods for IPCC --- embassy-stm32/src/ipcc.rs | 66 +++++++---------- embassy-stm32/src/tl_mbox/ble.rs | 12 +-- embassy-stm32/src/tl_mbox/evt.rs | 5 +- embassy-stm32/src/tl_mbox/mm.rs | 14 ++-- embassy-stm32/src/tl_mbox/mod.rs | 90 ++++++++++------------- embassy-stm32/src/tl_mbox/shci.rs | 6 +- embassy-stm32/src/tl_mbox/sys.rs | 20 ++--- examples/stm32wb/src/bin/tl_mbox.rs | 8 +- examples/stm32wb/src/bin/tl_mbox_tx_rx.rs | 21 +++--- 9 files changed, 104 insertions(+), 138 deletions(-) diff --git a/embassy-stm32/src/ipcc.rs b/embassy-stm32/src/ipcc.rs index 2b9caf8e..ea33b32c 100644 --- a/embassy-stm32/src/ipcc.rs +++ b/embassy-stm32/src/ipcc.rs @@ -1,5 +1,3 @@ -use embassy_hal_common::{into_ref, Peripheral, PeripheralRef}; - use crate::ipcc::sealed::Instance; use crate::peripherals::IPCC; use crate::rcc::sealed::RccPeripheral; @@ -29,22 +27,10 @@ pub(crate) mod sealed { } } -pub struct Ipcc<'d> { - _peri: PeripheralRef<'d, IPCC>, -} +pub(crate) struct Ipcc; -impl<'d> Ipcc<'d> { - pub fn new(peri: impl Peripheral

+ 'd, _config: Config) -> Self { - Self::new_inner(peri) - } - - pub(crate) fn new_inner(peri: impl Peripheral

+ 'd) -> Self { - into_ref!(peri); - - Self { _peri: peri } - } - - pub fn init(&mut self) { +impl Ipcc { + pub(crate) fn init(_config: Config) { IPCC::enable(); IPCC::reset(); IPCC::set_cpu2(true); @@ -61,56 +47,60 @@ impl<'d> Ipcc<'d> { } } - pub fn c1_set_rx_channel(&mut self, channel: IpccChannel, enabled: bool) { + pub(crate) fn c1_set_rx_channel(channel: IpccChannel, enabled: bool) { let regs = IPCC::regs(); // If bit is set to 1 then interrupt is disabled unsafe { regs.cpu(0).mr().modify(|w| w.set_chom(channel as usize, !enabled)) } } - pub fn c1_get_rx_channel(&self, channel: IpccChannel) -> bool { + pub(crate) fn c1_get_rx_channel(channel: IpccChannel) -> bool { let regs = IPCC::regs(); // If bit is set to 1 then interrupt is disabled unsafe { !regs.cpu(0).mr().read().chom(channel as usize) } } - pub fn c2_set_rx_channel(&mut self, channel: IpccChannel, enabled: bool) { + #[allow(dead_code)] + pub(crate) fn c2_set_rx_channel(channel: IpccChannel, enabled: bool) { let regs = IPCC::regs(); // If bit is set to 1 then interrupt is disabled unsafe { regs.cpu(1).mr().modify(|w| w.set_chom(channel as usize, !enabled)) } } - pub fn c2_get_rx_channel(&self, channel: IpccChannel) -> bool { + #[allow(dead_code)] + pub(crate) fn c2_get_rx_channel(channel: IpccChannel) -> bool { let regs = IPCC::regs(); // If bit is set to 1 then interrupt is disabled unsafe { !regs.cpu(1).mr().read().chom(channel as usize) } } - pub fn c1_set_tx_channel(&mut self, channel: IpccChannel, enabled: bool) { + pub(crate) fn c1_set_tx_channel(channel: IpccChannel, enabled: bool) { let regs = IPCC::regs(); // If bit is set to 1 then interrupt is disabled unsafe { regs.cpu(0).mr().modify(|w| w.set_chfm(channel as usize, !enabled)) } } - pub fn c1_get_tx_channel(&self, channel: IpccChannel) -> bool { + pub(crate) fn c1_get_tx_channel(channel: IpccChannel) -> bool { let regs = IPCC::regs(); // If bit is set to 1 then interrupt is disabled unsafe { !regs.cpu(0).mr().read().chfm(channel as usize) } } - pub fn c2_set_tx_channel(&mut self, channel: IpccChannel, enabled: bool) { + #[allow(dead_code)] + pub(crate) fn c2_set_tx_channel(channel: IpccChannel, enabled: bool) { let regs = IPCC::regs(); // If bit is set to 1 then interrupt is disabled unsafe { regs.cpu(1).mr().modify(|w| w.set_chfm(channel as usize, !enabled)) } } - pub fn c2_get_tx_channel(&self, channel: IpccChannel) -> bool { + #[allow(dead_code)] + pub(crate) fn c2_get_tx_channel(channel: IpccChannel) -> bool { let regs = IPCC::regs(); // If bit is set to 1 then interrupt is disabled @@ -118,53 +108,51 @@ impl<'d> Ipcc<'d> { } /// clears IPCC receive channel status for CPU1 - pub fn c1_clear_flag_channel(&mut self, channel: IpccChannel) { + pub(crate) fn c1_clear_flag_channel(channel: IpccChannel) { let regs = IPCC::regs(); unsafe { regs.cpu(0).scr().write(|w| w.set_chc(channel as usize, true)) } } + #[allow(dead_code)] /// clears IPCC receive channel status for CPU2 - pub fn c2_clear_flag_channel(&mut self, channel: IpccChannel) { + pub(crate) fn c2_clear_flag_channel(channel: IpccChannel) { let regs = IPCC::regs(); unsafe { regs.cpu(1).scr().write(|w| w.set_chc(channel as usize, true)) } } - pub fn c1_set_flag_channel(&mut self, channel: IpccChannel) { + pub(crate) fn c1_set_flag_channel(channel: IpccChannel) { let regs = IPCC::regs(); unsafe { regs.cpu(0).scr().write(|w| w.set_chs(channel as usize, true)) } } - pub fn c2_set_flag_channel(&mut self, channel: IpccChannel) { + #[allow(dead_code)] + pub(crate) fn c2_set_flag_channel(channel: IpccChannel) { let regs = IPCC::regs(); unsafe { regs.cpu(1).scr().write(|w| w.set_chs(channel as usize, true)) } } - pub fn c1_is_active_flag(&self, channel: IpccChannel) -> bool { + pub(crate) fn c1_is_active_flag(channel: IpccChannel) -> bool { let regs = IPCC::regs(); unsafe { regs.cpu(0).sr().read().chf(channel as usize) } } - pub fn c2_is_active_flag(&self, channel: IpccChannel) -> bool { + pub(crate) fn c2_is_active_flag(channel: IpccChannel) -> bool { let regs = IPCC::regs(); unsafe { regs.cpu(1).sr().read().chf(channel as usize) } } - pub fn is_tx_pending(&self, channel: IpccChannel) -> bool { - !self.c1_is_active_flag(channel) && self.c1_get_tx_channel(channel) + pub(crate) fn is_tx_pending(channel: IpccChannel) -> bool { + !Self::c1_is_active_flag(channel) && Self::c1_get_tx_channel(channel) } - pub fn is_rx_pending(&self, channel: IpccChannel) -> bool { - self.c2_is_active_flag(channel) && self.c1_get_rx_channel(channel) - } - - pub fn as_mut_ptr(&self) -> *mut Self { - unsafe { &mut core::ptr::read(self) as *mut _ } + pub(crate) fn is_rx_pending(channel: IpccChannel) -> bool { + Self::c2_is_active_flag(channel) && Self::c1_get_rx_channel(channel) } } diff --git a/embassy-stm32/src/tl_mbox/ble.rs b/embassy-stm32/src/tl_mbox/ble.rs index d8bf14d4..5cc0bb20 100644 --- a/embassy-stm32/src/tl_mbox/ble.rs +++ b/embassy-stm32/src/tl_mbox/ble.rs @@ -16,7 +16,7 @@ use crate::tl_mbox::cmd::CmdPacket; pub struct Ble; impl Ble { - pub(crate) fn new(ipcc: &mut Ipcc) -> Self { + pub(crate) fn new() -> Self { unsafe { LinkedListNode::init_head(EVT_QUEUE.as_mut_ptr()); @@ -28,12 +28,12 @@ impl Ble { }); } - ipcc.c1_set_rx_channel(channels::cpu2::IPCC_BLE_EVENT_CHANNEL, true); + Ipcc::c1_set_rx_channel(channels::cpu2::IPCC_BLE_EVENT_CHANNEL, true); Ble } - pub(crate) fn evt_handler(ipcc: &mut Ipcc) { + pub(crate) fn evt_handler() { unsafe { let mut node_ptr = core::ptr::null_mut(); let node_ptr_ptr: *mut _ = &mut node_ptr; @@ -48,10 +48,10 @@ impl Ble { } } - ipcc.c1_clear_flag_channel(channels::cpu2::IPCC_BLE_EVENT_CHANNEL); + Ipcc::c1_clear_flag_channel(channels::cpu2::IPCC_BLE_EVENT_CHANNEL); } - pub(crate) fn send_cmd(ipcc: &mut Ipcc, buf: &[u8]) { + pub(crate) fn send_cmd(buf: &[u8]) { unsafe { let pcmd_buffer: *mut CmdPacket = (*TL_REF_TABLE.assume_init().ble_table).pcmd_buffer; let pcmd_serial: *mut CmdSerial = &mut (*pcmd_buffer).cmd_serial; @@ -63,6 +63,6 @@ impl Ble { cmd_packet.cmd_serial.ty = TlPacketType::BleCmd as u8; } - ipcc.c1_set_flag_channel(channels::cpu1::IPCC_BLE_CMD_CHANNEL); + Ipcc::c1_set_flag_channel(channels::cpu1::IPCC_BLE_CMD_CHANNEL); } } diff --git a/embassy-stm32/src/tl_mbox/evt.rs b/embassy-stm32/src/tl_mbox/evt.rs index 770133f2..04feb3e6 100644 --- a/embassy-stm32/src/tl_mbox/evt.rs +++ b/embassy-stm32/src/tl_mbox/evt.rs @@ -131,9 +131,6 @@ impl EvtBox { impl Drop for EvtBox { fn drop(&mut self) { - use crate::ipcc::Ipcc; - - let mut ipcc = Ipcc::new_inner(unsafe { crate::Peripherals::steal() }.IPCC); - mm::MemoryManager::evt_drop(self.ptr, &mut ipcc); + mm::MemoryManager::evt_drop(self.ptr); } } diff --git a/embassy-stm32/src/tl_mbox/mm.rs b/embassy-stm32/src/tl_mbox/mm.rs index f99ffa39..6e0818cf 100644 --- a/embassy-stm32/src/tl_mbox/mm.rs +++ b/embassy-stm32/src/tl_mbox/mm.rs @@ -30,27 +30,27 @@ impl MemoryManager { MemoryManager } - pub fn evt_handler(ipcc: &mut Ipcc) { - ipcc.c1_set_tx_channel(channels::cpu1::IPCC_MM_RELEASE_BUFFER_CHANNEL, false); + pub fn evt_handler() { + Ipcc::c1_set_tx_channel(channels::cpu1::IPCC_MM_RELEASE_BUFFER_CHANNEL, false); Self::send_free_buf(); - ipcc.c1_set_flag_channel(channels::cpu1::IPCC_MM_RELEASE_BUFFER_CHANNEL); + Ipcc::c1_set_flag_channel(channels::cpu1::IPCC_MM_RELEASE_BUFFER_CHANNEL); } - pub fn evt_drop(evt: *mut EvtPacket, ipcc: &mut Ipcc) { + pub fn evt_drop(evt: *mut EvtPacket) { unsafe { let list_node = evt.cast(); LinkedListNode::remove_tail(LOCAL_FREE_BUF_QUEUE.as_mut_ptr(), list_node); } - let channel_is_busy = ipcc.c1_is_active_flag(channels::cpu1::IPCC_MM_RELEASE_BUFFER_CHANNEL); + let channel_is_busy = Ipcc::c1_is_active_flag(channels::cpu1::IPCC_MM_RELEASE_BUFFER_CHANNEL); // postpone event buffer freeing to IPCC interrupt handler if channel_is_busy { - ipcc.c1_set_tx_channel(channels::cpu1::IPCC_MM_RELEASE_BUFFER_CHANNEL, true); + Ipcc::c1_set_tx_channel(channels::cpu1::IPCC_MM_RELEASE_BUFFER_CHANNEL, true); } else { Self::send_free_buf(); - ipcc.c1_set_flag_channel(channels::cpu1::IPCC_MM_RELEASE_BUFFER_CHANNEL); + Ipcc::c1_set_flag_channel(channels::cpu1::IPCC_MM_RELEASE_BUFFER_CHANNEL); } } diff --git a/embassy-stm32/src/tl_mbox/mod.rs b/embassy-stm32/src/tl_mbox/mod.rs index dc6104cc..1c927efa 100644 --- a/embassy-stm32/src/tl_mbox/mod.rs +++ b/embassy-stm32/src/tl_mbox/mod.rs @@ -1,6 +1,7 @@ use core::mem::MaybeUninit; use bit_field::BitField; +use embassy_cortex_m::interrupt::{Interrupt, InterruptExt}; use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; use embassy_sync::channel::Channel; @@ -12,7 +13,7 @@ use self::shci::{shci_ble_init, ShciBleInitCmdParam}; use self::sys::Sys; use self::unsafe_linked_list::LinkedListNode; use crate::interrupt; -use crate::ipcc::Ipcc; +use crate::ipcc::{Config, Ipcc}; mod ble; mod channels; @@ -58,13 +59,30 @@ pub struct FusInfoTable { pub struct ReceiveInterruptHandler {} impl interrupt::Handler for ReceiveInterruptHandler { - unsafe fn on_interrupt() {} + unsafe fn on_interrupt() { + if Ipcc::is_rx_pending(channels::cpu2::IPCC_SYSTEM_EVENT_CHANNEL) { + sys::Sys::evt_handler(); + } else if Ipcc::is_rx_pending(channels::cpu2::IPCC_BLE_EVENT_CHANNEL) { + ble::Ble::evt_handler(); + } else { + todo!() + } + } } pub struct TransmitInterruptHandler {} impl interrupt::Handler for TransmitInterruptHandler { - unsafe fn on_interrupt() {} + unsafe fn on_interrupt() { + if Ipcc::is_tx_pending(channels::cpu1::IPCC_SYSTEM_CMD_RSP_CHANNEL) { + // TODO: handle this case + let _ = sys::Sys::cmd_evt_handler(); + } else if Ipcc::is_tx_pending(channels::cpu1::IPCC_MM_RELEASE_BUFFER_CHANNEL) { + mm::MemoryManager::evt_handler(); + } else { + todo!() + } + } } /// # Version @@ -298,9 +316,9 @@ pub struct TlMbox { impl TlMbox { /// initializes low-level transport between CPU1 and BLE stack on CPU2 pub fn init( - ipcc: &mut Ipcc, _irqs: impl interrupt::Binding + interrupt::Binding, + config: Config, ) -> TlMbox { unsafe { TL_REF_TABLE = MaybeUninit::new(RefTable { @@ -336,29 +354,18 @@ impl TlMbox { HCI_ACL_DATA_BUFFER = MaybeUninit::zeroed(); } - ipcc.init(); + Ipcc::init(config); - let _sys = Sys::new(ipcc); - let _ble = Ble::new(ipcc); + let _sys = Sys::new(); + let _ble = Ble::new(); let _mm = MemoryManager::new(); - // rx_irq.disable(); - // tx_irq.disable(); - // - // rx_irq.set_handler_context(ipcc.as_mut_ptr() as *mut ()); - // tx_irq.set_handler_context(ipcc.as_mut_ptr() as *mut ()); - // - // rx_irq.set_handler(|ipcc| { - // let ipcc: &mut Ipcc = unsafe { &mut *ipcc.cast() }; - // Self::interrupt_ipcc_rx_handler(ipcc); - // }); - // tx_irq.set_handler(|ipcc| { - // let ipcc: &mut Ipcc = unsafe { &mut *ipcc.cast() }; - // Self::interrupt_ipcc_tx_handler(ipcc); - // }); - // - // rx_irq.enable(); - // tx_irq.enable(); + // enable interrupts + unsafe { crate::interrupt::IPCC_C1_RX::steal() }.unpend(); + unsafe { crate::interrupt::IPCC_C1_TX::steal() }.unpend(); + + unsafe { crate::interrupt::IPCC_C1_RX::steal() }.enable(); + unsafe { crate::interrupt::IPCC_C1_TX::steal() }.enable(); TlMbox { _sys, _ble, _mm } } @@ -374,42 +381,19 @@ impl TlMbox { } } - pub fn shci_ble_init(&self, ipcc: &mut Ipcc, param: ShciBleInitCmdParam) { - shci_ble_init(ipcc, param); + pub fn shci_ble_init(&self, param: ShciBleInitCmdParam) { + shci_ble_init(param); } - pub fn send_ble_cmd(&self, ipcc: &mut Ipcc, buf: &[u8]) { - ble::Ble::send_cmd(ipcc, buf); + pub fn send_ble_cmd(&self, buf: &[u8]) { + ble::Ble::send_cmd(buf); } - // pub fn send_sys_cmd(&self, ipcc: &mut Ipcc, buf: &[u8]) { - // sys::Sys::send_cmd(ipcc, buf); + // pub fn send_sys_cmd(&self, buf: &[u8]) { + // sys::Sys::send_cmd(buf); // } pub async fn read(&self) -> EvtBox { TL_CHANNEL.recv().await } - - #[allow(dead_code)] - fn interrupt_ipcc_rx_handler(ipcc: &mut Ipcc) { - if ipcc.is_rx_pending(channels::cpu2::IPCC_SYSTEM_EVENT_CHANNEL) { - sys::Sys::evt_handler(ipcc); - } else if ipcc.is_rx_pending(channels::cpu2::IPCC_BLE_EVENT_CHANNEL) { - ble::Ble::evt_handler(ipcc); - } else { - todo!() - } - } - - #[allow(dead_code)] - fn interrupt_ipcc_tx_handler(ipcc: &mut Ipcc) { - if ipcc.is_tx_pending(channels::cpu1::IPCC_SYSTEM_CMD_RSP_CHANNEL) { - // TODO: handle this case - let _ = sys::Sys::cmd_evt_handler(ipcc); - } else if ipcc.is_tx_pending(channels::cpu1::IPCC_MM_RELEASE_BUFFER_CHANNEL) { - mm::MemoryManager::evt_handler(ipcc); - } else { - todo!() - } - } } diff --git a/embassy-stm32/src/tl_mbox/shci.rs b/embassy-stm32/src/tl_mbox/shci.rs index 61fd9e4a..3d7e994a 100644 --- a/embassy-stm32/src/tl_mbox/shci.rs +++ b/embassy-stm32/src/tl_mbox/shci.rs @@ -76,7 +76,7 @@ pub struct ShciBleInitCmdPacket { param: ShciBleInitCmdParam, } -pub fn shci_ble_init(ipcc: &mut Ipcc, param: ShciBleInitCmdParam) { +pub fn shci_ble_init(param: ShciBleInitCmdParam) { let mut packet = ShciBleInitCmdPacket { header: ShciHeader::default(), param, @@ -95,7 +95,7 @@ pub fn shci_ble_init(ipcc: &mut Ipcc, param: ShciBleInitCmdParam) { cmd_buf.cmd_serial.ty = TlPacketType::SysCmd as u8; - ipcc.c1_set_flag_channel(channels::cpu1::IPCC_SYSTEM_CMD_RSP_CHANNEL); - ipcc.c1_set_tx_channel(channels::cpu1::IPCC_SYSTEM_CMD_RSP_CHANNEL, true); + Ipcc::c1_set_flag_channel(channels::cpu1::IPCC_SYSTEM_CMD_RSP_CHANNEL); + Ipcc::c1_set_tx_channel(channels::cpu1::IPCC_SYSTEM_CMD_RSP_CHANNEL, true); } } diff --git a/embassy-stm32/src/tl_mbox/sys.rs b/embassy-stm32/src/tl_mbox/sys.rs index 31ebde72..79e25714 100644 --- a/embassy-stm32/src/tl_mbox/sys.rs +++ b/embassy-stm32/src/tl_mbox/sys.rs @@ -12,7 +12,7 @@ use crate::ipcc::Ipcc; pub struct Sys; impl Sys { - pub(crate) fn new(ipcc: &mut Ipcc) -> Self { + pub(crate) fn new() -> Self { unsafe { LinkedListNode::init_head(SYSTEM_EVT_QUEUE.as_mut_ptr()); @@ -22,12 +22,12 @@ impl Sys { }); } - ipcc.c1_set_rx_channel(channels::cpu2::IPCC_SYSTEM_EVENT_CHANNEL, true); + Ipcc::c1_set_rx_channel(channels::cpu2::IPCC_SYSTEM_EVENT_CHANNEL, true); Sys } - pub(crate) fn evt_handler(ipcc: &mut Ipcc) { + pub(crate) fn evt_handler() { unsafe { let mut node_ptr = core::ptr::null_mut(); let node_ptr_ptr: *mut _ = &mut node_ptr; @@ -43,11 +43,11 @@ impl Sys { } } - ipcc.c1_clear_flag_channel(channels::cpu2::IPCC_SYSTEM_EVENT_CHANNEL); + Ipcc::c1_clear_flag_channel(channels::cpu2::IPCC_SYSTEM_EVENT_CHANNEL); } - pub(crate) fn cmd_evt_handler(ipcc: &mut Ipcc) -> CcEvt { - ipcc.c1_set_tx_channel(channels::cpu1::IPCC_SYSTEM_CMD_RSP_CHANNEL, false); + pub(crate) fn cmd_evt_handler() -> CcEvt { + Ipcc::c1_set_tx_channel(channels::cpu1::IPCC_SYSTEM_CMD_RSP_CHANNEL, false); // ST's command response data structure is really convoluted. // @@ -68,11 +68,11 @@ impl Sys { } #[allow(dead_code)] - pub(crate) fn send_cmd(ipcc: &mut Ipcc, buf: &[u8]) { + pub(crate) fn send_cmd(buf: &[u8]) { unsafe { // TODO: check this let cmd_buffer = &mut *(*TL_REF_TABLE.assume_init().sys_table).pcmd_buffer; - let cmd_serial: *mut CmdSerial = &mut (*cmd_buffer).cmd_serial; + let cmd_serial: *mut CmdSerial = &mut cmd_buffer.cmd_serial; let cmd_serial_buf = cmd_serial.cast(); core::ptr::copy(buf.as_ptr(), cmd_serial_buf, buf.len()); @@ -80,8 +80,8 @@ impl Sys { let cmd_packet = &mut *(*TL_REF_TABLE.assume_init().sys_table).pcmd_buffer; cmd_packet.cmd_serial.ty = TlPacketType::SysCmd as u8; - ipcc.c1_set_flag_channel(channels::cpu1::IPCC_SYSTEM_CMD_RSP_CHANNEL); - ipcc.c1_set_tx_channel(channels::cpu1::IPCC_SYSTEM_CMD_RSP_CHANNEL, true); + Ipcc::c1_set_flag_channel(channels::cpu1::IPCC_SYSTEM_CMD_RSP_CHANNEL); + Ipcc::c1_set_tx_channel(channels::cpu1::IPCC_SYSTEM_CMD_RSP_CHANNEL, true); } } } diff --git a/examples/stm32wb/src/bin/tl_mbox.rs b/examples/stm32wb/src/bin/tl_mbox.rs index 326e4be8..18d93a27 100644 --- a/examples/stm32wb/src/bin/tl_mbox.rs +++ b/examples/stm32wb/src/bin/tl_mbox.rs @@ -4,7 +4,7 @@ use defmt::*; use embassy_executor::Spawner; -use embassy_stm32::ipcc::{Config, Ipcc}; +use embassy_stm32::ipcc::Config; use embassy_stm32::tl_mbox::TlMbox; use embassy_stm32::{bind_interrupts, tl_mbox}; use embassy_time::{Duration, Timer}; @@ -41,13 +41,11 @@ async fn main(_spawner: Spawner) { 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()); + let _p = embassy_stm32::init(Default::default()); info!("Hello World!"); let config = Config::default(); - let mut ipcc = Ipcc::new(p.IPCC, config); - - let mbox = TlMbox::init(&mut ipcc, Irqs); + let mbox = TlMbox::init(Irqs, config); loop { let wireless_fw_info = mbox.wireless_fw_info(); diff --git a/examples/stm32wb/src/bin/tl_mbox_tx_rx.rs b/examples/stm32wb/src/bin/tl_mbox_tx_rx.rs index 7a69f26b..41c450a5 100644 --- a/examples/stm32wb/src/bin/tl_mbox_tx_rx.rs +++ b/examples/stm32wb/src/bin/tl_mbox_tx_rx.rs @@ -4,7 +4,7 @@ use defmt::*; use embassy_executor::Spawner; -use embassy_stm32::ipcc::{Config, Ipcc}; +use embassy_stm32::ipcc::Config; use embassy_stm32::tl_mbox::TlMbox; use embassy_stm32::{bind_interrupts, tl_mbox}; use {defmt_rtt as _, panic_probe as _}; @@ -40,16 +40,11 @@ async fn main(_spawner: Spawner) { 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()); + let _p = embassy_stm32::init(Default::default()); info!("Hello World!"); let config = Config::default(); - let mut ipcc = Ipcc::new(p.IPCC, config); - - let mbox = TlMbox::init(&mut ipcc, Irqs); - - // initialize ble stack, does not return a response - mbox.shci_ble_init(&mut ipcc, Default::default()); + let mbox = TlMbox::init(Irqs, config); info!("waiting for coprocessor to boot"); let event_box = mbox.read().await; @@ -74,10 +69,11 @@ async fn main(_spawner: Spawner) { ); } - mbox.shci_ble_init(&mut ipcc, Default::default()); + // initialize ble stack, does not return a response + mbox.shci_ble_init(Default::default()); info!("resetting BLE"); - mbox.send_ble_cmd(&mut ipcc, &[0x01, 0x03, 0x0c, 0x00, 0x00]); + mbox.send_ble_cmd(&[0x01, 0x03, 0x0c, 0x00, 0x00]); let event_box = mbox.read().await; @@ -92,7 +88,10 @@ async fn main(_spawner: Spawner) { info!( "==> kind: {:#04x}, code: {:#04x}, payload_length: {}, payload: {:#04x}", - kind, code, payload_len, payload + kind, + code, + payload_len, + payload[3..] ); loop {} From 984cd47b417a7bab986ff11b589b88fcae2940b9 Mon Sep 17 00:00:00 2001 From: goueslati Date: Fri, 26 May 2023 10:03:01 +0100 Subject: [PATCH 139/178] stm32/ipcc: update test --- tests/stm32/src/bin/ble.rs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/tests/stm32/src/bin/ble.rs b/tests/stm32/src/bin/ble.rs index aedf9a38..db5cc611 100644 --- a/tests/stm32/src/bin/ble.rs +++ b/tests/stm32/src/bin/ble.rs @@ -7,24 +7,24 @@ #[path = "../example_common.rs"] mod example_common; use embassy_executor::Spawner; -use embassy_stm32::interrupt; -use embassy_stm32::ipcc::{Config, Ipcc}; +use embassy_stm32::ipcc::Config; use embassy_stm32::tl_mbox::TlMbox; +use embassy_stm32::{bind_interrupts, tl_mbox}; use embassy_time::{Duration, Timer}; use example_common::*; +bind_interrupts!(struct Irqs{ + IPCC_C1_RX => tl_mbox::ReceiveInterruptHandler; + IPCC_C1_TX => tl_mbox::TransmitInterruptHandler; +}); + #[embassy_executor::main] async fn main(_spawner: Spawner) { - let p = embassy_stm32::init(config()); + let _p = embassy_stm32::init(config()); info!("Hello World!"); let config = Config::default(); - let mut ipcc = Ipcc::new(p.IPCC, config); - - let rx_irq = interrupt::take!(IPCC_C1_RX); - let tx_irq = interrupt::take!(IPCC_C1_TX); - - let mbox = TlMbox::init(&mut ipcc, rx_irq, tx_irq); + let mbox = TlMbox::init(Irqs, config); loop { let wireless_fw_info = mbox.wireless_fw_info(); From 66304a102d628f2e61f9e38bb95eb11a2663335d Mon Sep 17 00:00:00 2001 From: goueslati Date: Fri, 26 May 2023 11:26:58 +0100 Subject: [PATCH 140/178] Revert "Merge branch 'tl_mbox' into ipcc" This reverts commit 859e539f85c7f4770050cc11f83fe1f6d040cd1d, reversing changes made to 984cd47b417a7bab986ff11b589b88fcae2940b9. --- embassy-stm32/Cargo.toml | 10 -- embassy-stm32/src/tl_mbox/ble.rs | 11 -- embassy-stm32/src/tl_mbox/channels.rs | 144 ++++++++-------------- embassy-stm32/src/tl_mbox/mac_802_15_4.rs | 82 ------------ embassy-stm32/src/tl_mbox/mm.rs | 2 +- embassy-stm32/src/tl_mbox/mod.rs | 92 ++++---------- embassy-stm32/src/tl_mbox/sys.rs | 1 + 7 files changed, 75 insertions(+), 267 deletions(-) delete mode 100644 embassy-stm32/src/tl_mbox/mac_802_15_4.rs diff --git a/embassy-stm32/Cargo.toml b/embassy-stm32/Cargo.toml index 653f328c..0d7e03bf 100644 --- a/embassy-stm32/Cargo.toml +++ b/embassy-stm32/Cargo.toml @@ -110,16 +110,6 @@ unstable-pac = [] # Implement embedded-hal-async traits if `nightly` is set as well. unstable-traits = ["embedded-hal-1", "dep:embedded-hal-nb"] -# stm32wb specific -# support for wireless stacks -ble = [] -thread = [] -lld-tests = [] -ble-lld = [] -mac-802_15_4 = [] -zigbee = [] -traces = [] - # Chip-selection features stm32c011d6 = [ "stm32-metapac/stm32c011d6" ] stm32c011f4 = [ "stm32-metapac/stm32c011f4" ] diff --git a/embassy-stm32/src/tl_mbox/ble.rs b/embassy-stm32/src/tl_mbox/ble.rs index 0699d186..5cc0bb20 100644 --- a/embassy-stm32/src/tl_mbox/ble.rs +++ b/embassy-stm32/src/tl_mbox/ble.rs @@ -65,15 +65,4 @@ impl Ble { Ipcc::c1_set_flag_channel(channels::cpu1::IPCC_BLE_CMD_CHANNEL); } - - pub(crate) fn send_acl_data() { - unsafe { - (*(*TL_REF_TABLE.assume_init().ble_table).phci_acl_data_buffer) - .acl_data_serial - .ty = TlPacketType::AclData as u8; - } - - Ipcc::c1_set_flag_channel(channels::Cpu1Channel::HciAclData.into()); - Ipcc::c1_set_tx_channel(channels::Cpu1Channel::HciAclData.into(), true); - } } diff --git a/embassy-stm32/src/tl_mbox/channels.rs b/embassy-stm32/src/tl_mbox/channels.rs index 94e97230..aaa6ce17 100644 --- a/embassy-stm32/src/tl_mbox/channels.rs +++ b/embassy-stm32/src/tl_mbox/channels.rs @@ -49,104 +49,56 @@ //! | | //! -use crate::ipcc::IpccChannel; +pub mod cpu1 { + use crate::ipcc::IpccChannel; -pub enum Cpu1Channel { - BleCmd, - SystemCmdRsp, - #[cfg(feature = "thread")] - ThreadOtCmdRsp, - #[cfg(feature = "zigbee")] - ZigbeeCmdAppli, - MmReleaseBuffer, - #[cfg(feature = "mac-802_15_4")] - Mac802_15_4cmdRsp, - #[cfg(feature = "thread")] - ThreadCliCmd, - #[cfg(feature = "lld-tests")] - LldTestsCliCmd, - #[cfg(feature = "ble-lld")] - BleLldCmd, - HciAclData, + // Not used currently but reserved + pub const IPCC_BLE_CMD_CHANNEL: IpccChannel = IpccChannel::Channel1; + // Not used currently but reserved + pub const IPCC_SYSTEM_CMD_RSP_CHANNEL: IpccChannel = IpccChannel::Channel2; + #[allow(dead_code)] // Not used currently but reserved + pub const IPCC_THREAD_OT_CMD_RSP_CHANNEL: IpccChannel = IpccChannel::Channel3; + #[allow(dead_code)] // Not used currently but reserved + pub const IPCC_ZIGBEE_CMD_APPLI_CHANNEL: IpccChannel = IpccChannel::Channel3; + #[allow(dead_code)] // Not used currently but reserved + pub const IPCC_MAC_802_15_4_CMD_RSP_CHANNEL: IpccChannel = IpccChannel::Channel3; + // Not used currently but reserved + pub const IPCC_MM_RELEASE_BUFFER_CHANNEL: IpccChannel = IpccChannel::Channel4; + #[allow(dead_code)] // Not used currently but reserved + pub const IPCC_THREAD_CLI_CMD_CHANNEL: IpccChannel = IpccChannel::Channel5; + #[allow(dead_code)] // Not used currently but reserved + pub const IPCC_LLDTESTS_CLI_CMD_CHANNEL: IpccChannel = IpccChannel::Channel5; + #[allow(dead_code)] // Not used currently but reserved + pub const IPCC_BLE_LLD_CMD_CHANNEL: IpccChannel = IpccChannel::Channel5; + #[allow(dead_code)] // Not used currently but reserved + pub const IPCC_HCI_ACL_DATA_CHANNEL: IpccChannel = IpccChannel::Channel6; } -impl From for IpccChannel { - fn from(value: Cpu1Channel) -> Self { - match value { - Cpu1Channel::BleCmd => IpccChannel::Channel1, - Cpu1Channel::SystemCmdRsp => IpccChannel::Channel2, - #[cfg(feature = "thread")] - Cpu1Channel::ThreadOtCmdRsp => IpccChannel::Channel3, - #[cfg(feature = "zigbee")] - Cpu1Channel::ZigbeeCmdAppli => IpccChannel::Channel3, - #[cfg(feature = "mac-802_15_4")] - Cpu1Channel::Mac802_15_4cmdRsp => IpccChannel::Channel3, - Cpu1Channel::MmReleaseBuffer => IpccChannel::Channel4, - #[cfg(feature = "thread")] - Cpu1Channel::ThreadCliCmd => IpccChannel::Channel5, - #[cfg(feature = "lld-tests")] - Cpu1Channel::LldTestsCliCmd => IpccChannel::Channel5, - #[cfg(feature = "ble-lld")] - Cpu1Channel::BleLldCmd => IpccChannel::Channel5, - Cpu1Channel::HciAclData => IpccChannel::Channel6, - } - } -} +pub mod cpu2 { + use crate::ipcc::IpccChannel; -pub enum Cpu2Channel { - BleEvent, - SystemEvent, - #[cfg(feature = "thread")] - ThreadNotifAck, - #[cfg(feature = "zigbee")] - ZigbeeAppliNotifAck, - #[cfg(feature = "mac-802_15_4")] - Mac802_15_4NotifAck, - #[cfg(feature = "lld-tests")] - LldTestsM0Cmd, - #[cfg(feature = "ble-lld")] - BleLldM0Cmd, - #[cfg(feature = "traces")] - Traces, - #[cfg(feature = "thread")] - ThreadCliNotifAck, - #[cfg(feature = "lld-tests")] - LldTestsCliRsp, - #[cfg(feature = "ble-lld")] - BleLldCliRsp, - #[cfg(feature = "ble-lld")] - BleLldRsp, - #[cfg(feature = "zigbee")] - ZigbeeM0Request, -} - -impl From for IpccChannel { - fn from(value: Cpu2Channel) -> Self { - match value { - Cpu2Channel::BleEvent => IpccChannel::Channel1, - Cpu2Channel::SystemEvent => IpccChannel::Channel2, - #[cfg(feature = "thread")] - Cpu2Channel::ThreadNotifAck => IpccChannel::Channel3, - #[cfg(feature = "zigbee")] - Cpu2Channel::ZigbeeAppliNotifAck => IpccChannel::Channel3, - #[cfg(feature = "mac-802_15_4")] - Cpu2Channel::Mac802_15_4NotifAck => IpccChannel::Channel3, - #[cfg(feature = "lld-tests")] - Cpu2Channel::LldTestsM0Cmd => IpccChannel::Channel3, - #[cfg(feature = "ble-lld")] - Cpu2Channel::BleLldM0Cmd => IpccChannel::Channel3, - #[cfg(feature = "traces")] - Cpu2Channel::Traces => IpccChannel::Channel4, - #[cfg(feature = "thread")] - Cpu2Channel::ThreadCliNotifAck => IpccChannel::Channel5, - #[cfg(feature = "lld-tests")] - Cpu2Channel::LldTestsCliRsp => IpccChannel::Channel5, - #[cfg(feature = "ble-lld")] - Cpu2Channel::BleLldCliRsp => IpccChannel::Channel5, - #[cfg(feature = "ble-lld")] - Cpu2Channel::BleLldRsp => IpccChannel::Channel5, - #[cfg(feature = "zigbee")] - Cpu2Channel::ZigbeeM0Request => IpccChannel::Channel5, - } - } + pub const IPCC_BLE_EVENT_CHANNEL: IpccChannel = IpccChannel::Channel1; + pub const IPCC_SYSTEM_EVENT_CHANNEL: IpccChannel = IpccChannel::Channel2; + #[allow(dead_code)] // Not used currently but reserved + pub const IPCC_THREAD_NOTIFICATION_ACK_CHANNEL: IpccChannel = IpccChannel::Channel3; + #[allow(dead_code)] // Not used currently but reserved + pub const IPCC_ZIGBEE_APPLI_NOTIF_ACK_CHANNEL: IpccChannel = IpccChannel::Channel3; + #[allow(dead_code)] // Not used currently but reserved + pub const IPCC_MAC_802_15_4_NOTIFICATION_ACK_CHANNEL: IpccChannel = IpccChannel::Channel3; + #[allow(dead_code)] // Not used currently but reserved + pub const IPCC_LDDTESTS_M0_CMD_CHANNEL: IpccChannel = IpccChannel::Channel3; + #[allow(dead_code)] // Not used currently but reserved + pub const IPCC_BLE_LLD_M0_CMD_CHANNEL: IpccChannel = IpccChannel::Channel3; + #[allow(dead_code)] // Not used currently but reserved + pub const IPCC_TRACES_CHANNEL: IpccChannel = IpccChannel::Channel4; + #[allow(dead_code)] // Not used currently but reserved + pub const IPCC_THREAD_CLI_NOTIFICATION_ACK_CHANNEL: IpccChannel = IpccChannel::Channel5; + #[allow(dead_code)] // Not used currently but reserved + pub const IPCC_LLDTESTS_CLI_RSP_CHANNEL: IpccChannel = IpccChannel::Channel5; + #[allow(dead_code)] // Not used currently but reserved + pub const IPCC_BLE_LLD_CLI_RSP_CHANNEL: IpccChannel = IpccChannel::Channel5; + #[allow(dead_code)] // Not used currently but reserved + pub const IPCC_BLE_LLD_RSP_CHANNEL: IpccChannel = IpccChannel::Channel5; + #[allow(dead_code)] // Not used currently but reserved + pub const IPCC_ZIGBEE_M0_REQUEST_CHANNEL: IpccChannel = IpccChannel::Channel5; } diff --git a/embassy-stm32/src/tl_mbox/mac_802_15_4.rs b/embassy-stm32/src/tl_mbox/mac_802_15_4.rs deleted file mode 100644 index 19f95113..00000000 --- a/embassy-stm32/src/tl_mbox/mac_802_15_4.rs +++ /dev/null @@ -1,82 +0,0 @@ -use core::mem::MaybeUninit; - -use embassy_futures::block_on; - -use super::cmd::{CmdPacket, CmdSerial}; -use super::consts::TlPacketType; -use super::evt::{EvtBox, EvtPacket}; -use super::unsafe_linked_list::LinkedListNode; -use super::{ - channels, Mac802_15_4Table, EVT_QUEUE, MAC_802_15_4_CMD_BUFFER, MAC_802_15_4_NOTIF_RSP_EVT_BUFFER, TL_CHANNEL, - TL_MAC_802_15_4_TABLE, TL_REF_TABLE, -}; -use crate::ipcc::Ipcc; - -pub struct Mac802_15_4; - -impl Mac802_15_4 { - pub(crate) fn init(ipcc: &mut Ipcc) -> Self { - unsafe { - LinkedListNode::init_head(EVT_QUEUE.as_mut_ptr()); - - TL_MAC_802_15_4_TABLE = MaybeUninit::new(Mac802_15_4Table { - pcmd_rsp_buffer: MAC_802_15_4_CMD_BUFFER.as_mut_ptr().cast(), - pnotack_buffer: MAC_802_15_4_NOTIF_RSP_EVT_BUFFER.as_mut_ptr().cast(), - evt_queue: EVT_QUEUE.as_ptr().cast(), - }); - } - - ipcc.c1_set_rx_channel(channels::Cpu2Channel::Mac802_15_4NotifAck.into(), true); - - Self - } - - pub(crate) fn notif_evt_handler(ipcc: &mut Ipcc) { - unsafe { - let notif_buffer: *mut EvtPacket = (*TL_REF_TABLE.assume_init().mac_802_15_4_table).pnotack_buffer.cast(); - let event = EvtBox::new(notif_buffer); - - block_on(TL_CHANNEL.send(event)); - } - - ipcc.c1_set_rx_channel(channels::Cpu2Channel::Mac802_15_4NotifAck.into(), false); - } - - pub(crate) fn cmd_evt_handler(ipcc: &mut Ipcc) { - unsafe { - let _notif_buffer = (*TL_REF_TABLE.assume_init().mac_802_15_4_table).pcmd_rsp_buffer; - - // NOTE: ST's HAL does nothing with this buffer, ?????? - } - - ipcc.c1_set_tx_channel(channels::Cpu1Channel::Mac802_15_4cmdRsp.into(), false); - } - - pub(crate) fn send_cmd(ipcc: &mut Ipcc, buf: &[u8]) { - unsafe { - let pcmd_buffer: *mut CmdPacket = (*TL_REF_TABLE.assume_init().mac_802_15_4_table).pcmd_rsp_buffer.cast(); - let pcmd_serial: *mut CmdSerial = &mut (*pcmd_buffer).cmd_serial; - let pcmd_serial_buf: *mut u8 = pcmd_serial.cast(); - - core::ptr::copy(buf.as_ptr(), pcmd_serial_buf, buf.len()); - - let cmd_packet: &mut CmdPacket = - &mut *(*TL_REF_TABLE.assume_init().mac_802_15_4_table).pcmd_rsp_buffer.cast(); - cmd_packet.cmd_serial.ty = TlPacketType::OtCmd as u8; - } - - ipcc.c1_set_flag_channel(channels::Cpu1Channel::Mac802_15_4cmdRsp.into()); - ipcc.c1_set_tx_channel(channels::Cpu1Channel::Mac802_15_4cmdRsp.into(), true); - } - - pub(crate) fn send_ack(ipcc: &mut Ipcc) { - // TODO - unsafe { - let packet: &mut CmdPacket = &mut *(*TL_REF_TABLE.assume_init().mac_802_15_4_table).pnotack_buffer.cast(); - packet.cmd_serial.ty = TlPacketType::OtAck as u8; - } - - ipcc.c1_clear_flag_channel(channels::Cpu2Channel::Mac802_15_4NotifAck.into()); - ipcc.c1_set_rx_channel(channels::Cpu2Channel::Mac802_15_4NotifAck.into(), true); - } -} diff --git a/embassy-stm32/src/tl_mbox/mm.rs b/embassy-stm32/src/tl_mbox/mm.rs index cb161ce5..6e0818cf 100644 --- a/embassy-stm32/src/tl_mbox/mm.rs +++ b/embassy-stm32/src/tl_mbox/mm.rs @@ -11,7 +11,7 @@ use crate::ipcc::Ipcc; pub struct MemoryManager; impl MemoryManager { - pub fn init() -> Self { + pub fn new() -> Self { unsafe { LinkedListNode::init_head(FREE_BUFF_QUEUE.as_mut_ptr()); LinkedListNode::init_head(LOCAL_FREE_BUF_QUEUE.as_mut_ptr()); diff --git a/embassy-stm32/src/tl_mbox/mod.rs b/embassy-stm32/src/tl_mbox/mod.rs index fe366f6d..1c927efa 100644 --- a/embassy-stm32/src/tl_mbox/mod.rs +++ b/embassy-stm32/src/tl_mbox/mod.rs @@ -5,12 +5,9 @@ use embassy_cortex_m::interrupt::{Interrupt, InterruptExt}; use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; use embassy_sync::channel::Channel; -#[cfg(feature = "ble")] use self::ble::Ble; use self::cmd::{AclDataPacket, CmdPacket}; use self::evt::{CsEvt, EvtBox}; -#[cfg(feature = "mac-802_15_4")] -use self::mac_802_15_4::Mac802_15_4; use self::mm::MemoryManager; use self::shci::{shci_ble_init, ShciBleInitCmdParam}; use self::sys::Sys; @@ -18,6 +15,7 @@ use self::unsafe_linked_list::LinkedListNode; use crate::interrupt; use crate::ipcc::{Config, Ipcc}; +mod ble; mod channels; mod cmd; mod consts; @@ -27,11 +25,6 @@ mod shci; mod sys; mod unsafe_linked_list; -#[cfg(feature = "ble")] -mod ble; -#[cfg(feature = "mac-802_15_4")] -mod mac_802_15_4; - pub type PacketHeader = LinkedListNode; const TL_PACKET_HEADER_SIZE: usize = core::mem::size_of::(); @@ -219,8 +212,8 @@ struct TracesTable { #[repr(C, packed)] struct Mac802_15_4Table { - pcmd_rsp_buffer: *mut u8, - pnotack_buffer: *mut u8, + pcmd_rsp_buffer: *const u8, + pnotack_buffer: *const u8, evt_queue: *const u8, } @@ -240,7 +233,6 @@ pub struct RefTable { ble_lld_table: *const BleLldTable, } -// -------------------- reference table -------------------- #[link_section = "TL_REF_TABLE"] pub static mut TL_REF_TABLE: MaybeUninit = MaybeUninit::uninit(); @@ -274,50 +266,38 @@ static mut TL_MAC_802_15_4_TABLE: MaybeUninit = MaybeUninit::u #[link_section = "MB_MEM1"] static mut TL_ZIGBEE_TABLE: MaybeUninit = MaybeUninit::uninit(); -// -------------------- tables -------------------- +#[allow(dead_code)] // Not used currently but reserved #[link_section = "MB_MEM1"] static mut FREE_BUFF_QUEUE: MaybeUninit = MaybeUninit::uninit(); +// not in shared RAM +static mut LOCAL_FREE_BUF_QUEUE: MaybeUninit = MaybeUninit::uninit(); + #[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_MEM1"] +#[link_section = "MB_MEM2"] static mut EVT_QUEUE: MaybeUninit = MaybeUninit::uninit(); #[link_section = "MB_MEM2"] static mut SYSTEM_EVT_QUEUE: MaybeUninit = MaybeUninit::uninit(); -// not in shared RAM -static mut LOCAL_FREE_BUF_QUEUE: MaybeUninit = MaybeUninit::uninit(); - -// -------------------- app tables -------------------- -#[link_section = "MB_MEM2"] -static mut EVT_POOL: MaybeUninit<[u8; POOL_SIZE]> = MaybeUninit::uninit(); - #[link_section = "MB_MEM2"] static mut SYS_CMD_BUF: MaybeUninit = MaybeUninit::uninit(); +#[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(); -#[cfg(feature = "mac-802_15_4")] -#[link_section = "MB_MEM2"] -static mut MAC_802_15_4_CMD_BUFFER: MaybeUninit = MaybeUninit::uninit(); - -#[cfg(feature = "mac-802_15_4")] -#[link_section = "MB_MEM2"] -static mut MAC_802_15_4_NOTIF_RSP_EVT_BUFFER: MaybeUninit<[u8; TL_PACKET_HEADER_SIZE + TL_EVT_HEADER_SIZE + 255]> = - MaybeUninit::uninit(); - -#[cfg(feature = "ble")] #[link_section = "MB_MEM2"] static mut BLE_SPARE_EVT_BUF: MaybeUninit<[u8; TL_PACKET_HEADER_SIZE + TL_EVT_HEADER_SIZE + 255]> = MaybeUninit::uninit(); -#[cfg(feature = "ble")] -#[link_section = "MB_MEM1"] +#[link_section = "MB_MEM2"] static mut BLE_CMD_BUFFER: MaybeUninit = MaybeUninit::uninit(); #[link_section = "MB_MEM2"] @@ -327,14 +307,10 @@ static mut HCI_ACL_DATA_BUFFER: MaybeUninit<[u8; TL_PACKET_HEADER_SIZE + 5 + 251 // TODO: get a better size, this is a placeholder pub(crate) static TL_CHANNEL: Channel = Channel::new(); -pub struct TlMbox; - -pub enum MailboxTarget { - Sys, - #[cfg(feature = "ble")] - Ble, - #[cfg(feature = "mac-802_15_4")] - Mac802_15_4, +pub struct TlMbox { + _sys: Sys, + _ble: Ble, + _mm: MemoryManager, } impl TlMbox { @@ -380,14 +356,9 @@ impl TlMbox { Ipcc::init(config); - Sys::init(); - MemoryManager::init(); - - #[cfg(feature = "ble")] - Ble::init(); - - #[cfg(feature = "mac-802_15_4")] - Mac802_15_4::init(); + let _sys = Sys::new(); + let _ble = Ble::new(); + let _mm = MemoryManager::new(); // enable interrupts unsafe { crate::interrupt::IPCC_C1_RX::steal() }.unpend(); @@ -396,7 +367,7 @@ impl TlMbox { unsafe { crate::interrupt::IPCC_C1_RX::steal() }.enable(); unsafe { crate::interrupt::IPCC_C1_TX::steal() }.enable(); - Self + TlMbox { _sys, _ble, _mm } } pub fn wireless_fw_info(&self) -> Option { @@ -410,30 +381,17 @@ impl TlMbox { } } - #[cfg(feature = "ble")] pub fn shci_ble_init(&self, param: ShciBleInitCmdParam) { shci_ble_init(param); } - pub fn send_cmd(&self, buf: &[u8], target: MailboxTarget) { - match target { - MailboxTarget::Sys => Sys::send_cmd(buf), - #[cfg(feature = "ble")] - MailboxTarget::Ble => Ble::send_cmd(buf), - #[cfg(feature = "mac-802_15_4")] - MailboxTarget::Mac802_15_4 => Mac802_15_4::send_cmd(buf), - } + pub fn send_ble_cmd(&self, buf: &[u8]) { + ble::Ble::send_cmd(buf); } - pub fn send_ack(&self, target: MailboxTarget) { - match target { - #[cfg(feature = "ble")] - MailboxTarget::Ble => Ble::send_acl_data(), - #[cfg(feature = "mac-802_15_4")] - MailboxTarget::Mac802_15_4 => Mac802_15_4::send_ack(), - MailboxTarget::Sys => { /* does nothing */ } - } - } + // pub fn send_sys_cmd(&self, buf: &[u8]) { + // sys::Sys::send_cmd(buf); + // } pub async fn read(&self) -> EvtBox { TL_CHANNEL.recv().await diff --git a/embassy-stm32/src/tl_mbox/sys.rs b/embassy-stm32/src/tl_mbox/sys.rs index 4ffbe8e6..79e25714 100644 --- a/embassy-stm32/src/tl_mbox/sys.rs +++ b/embassy-stm32/src/tl_mbox/sys.rs @@ -70,6 +70,7 @@ impl Sys { #[allow(dead_code)] pub(crate) fn send_cmd(buf: &[u8]) { unsafe { + // TODO: check this let cmd_buffer = &mut *(*TL_REF_TABLE.assume_init().sys_table).pcmd_buffer; let cmd_serial: *mut CmdSerial = &mut cmd_buffer.cmd_serial; let cmd_serial_buf = cmd_serial.cast(); From c19967dcf24d5223de5fd9b390371dc24aeccc1d Mon Sep 17 00:00:00 2001 From: xoviat Date: Sat, 27 May 2023 15:03:25 -0500 Subject: [PATCH 141/178] stm32/ipcc: extract tl_mbox linker file to embassy-stm32 --- embassy-stm32/build.rs | 10 ++++++ .../tl_mbox.x.in | 14 ++------ examples/stm32wb/Cargo.toml | 2 +- examples/stm32wb/build.rs | 36 ++++--------------- examples/stm32wb/memory.x | 35 ------------------ 5 files changed, 20 insertions(+), 77 deletions(-) rename tests/stm32/memory_ble.x => embassy-stm32/tl_mbox.x.in (58%) delete mode 100644 examples/stm32wb/memory.x diff --git a/embassy-stm32/build.rs b/embassy-stm32/build.rs index 54098172..9207cf3f 100644 --- a/embassy-stm32/build.rs +++ b/embassy-stm32/build.rs @@ -910,6 +910,16 @@ fn main() { println!("cargo:rustc-cfg={}x{}", &chip_name[..9], &chip_name[10..11]); } + // ======== + // stm32wb tl_mbox link sections + + if chip_name.starts_with("stm32wb") { + let out_file = out_dir.join("tl_mbox.x").to_string_lossy().to_string(); + fs::write(out_file, fs::read_to_string("tl_mbox.x.in").unwrap()).unwrap(); + println!("cargo:rustc-link-search={}", out_dir.display()); + println!("cargo:rerun-if-changed=tl_mbox.x.in"); + } + // ======= // Features for targeting groups of chips diff --git a/tests/stm32/memory_ble.x b/embassy-stm32/tl_mbox.x.in similarity index 58% rename from tests/stm32/memory_ble.x rename to embassy-stm32/tl_mbox.x.in index 4332e2c7..b6eecb42 100644 --- a/tests/stm32/memory_ble.x +++ b/embassy-stm32/tl_mbox.x.in @@ -1,21 +1,13 @@ -/* - Memory size for STM32WB55xG with 512K FLASH -*/ - -MEMORY +MEMORY { - FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 512K - RAM (xrw) : ORIGIN = 0x20000008, LENGTH = 0x2FFF8 RAM_SHARED (xrw) : ORIGIN = 0x20030000, LENGTH = 10K } -/* Place stack at the end of SRAM1 */ -_stack_start = ORIGIN(RAM) + LENGTH(RAM); - /* * Scatter the mailbox interface memory sections in shared memory */ -SECTIONS { +SECTIONS +{ TL_REF_TABLE (NOLOAD) : { *(TL_REF_TABLE) } >RAM_SHARED MB_MEM1 (NOLOAD) : { *(MB_MEM1) } >RAM_SHARED diff --git a/examples/stm32wb/Cargo.toml b/examples/stm32wb/Cargo.toml index 3c7e3e87..8cfac772 100644 --- a/examples/stm32wb/Cargo.toml +++ b/examples/stm32wb/Cargo.toml @@ -8,7 +8,7 @@ license = "MIT OR Apache-2.0" embassy-sync = { version = "0.2.0", path = "../../embassy-sync", features = ["defmt"] } embassy-executor = { version = "0.2.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } embassy-time = { version = "0.1.0", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } -embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["nightly", "defmt", "stm32wb55rg", "time-driver-any", "exti"] } +embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["nightly", "defmt", "stm32wb55rg", "time-driver-any", "memory-x", "exti"] } defmt = "0.3" defmt-rtt = "0.4" diff --git a/examples/stm32wb/build.rs b/examples/stm32wb/build.rs index 30691aa9..29b3a9b2 100644 --- a/examples/stm32wb/build.rs +++ b/examples/stm32wb/build.rs @@ -1,35 +1,11 @@ -//! This build script copies the `memory.x` file from the crate root into -//! a directory where the linker can always find it at build time. -//! For many projects this is optional, as the linker always searches the -//! project root directory -- wherever `Cargo.toml` is. However, if you -//! are using a workspace or have a more complicated build setup, this -//! build script becomes required. Additionally, by requesting that -//! Cargo re-run the build script whenever `memory.x` is changed, -//! updating `memory.x` ensures a rebuild of the application with the -//! new memory settings. - -use std::env; -use std::fs::File; -use std::io::Write; -use std::path::PathBuf; - -fn main() { - // Put `memory.x` in our output directory and ensure it's - // on the linker search path. - let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap()); - File::create(out.join("memory.x")) - .unwrap() - .write_all(include_bytes!("memory.x")) - .unwrap(); - println!("cargo:rustc-link-search={}", out.display()); - - // By default, Cargo will re-run a build script whenever - // any file in the project changes. By specifying `memory.x` - // here, we ensure the build script is only re-run when - // `memory.x` is changed. - println!("cargo:rerun-if-changed=memory.x"); +use std::error::Error; +fn main() -> Result<(), Box> { println!("cargo:rustc-link-arg-bins=--nmagic"); println!("cargo:rustc-link-arg-bins=-Tlink.x"); + println!("cargo:rerun-if-changed=link.x"); + println!("cargo:rustc-link-arg-bins=-Ttl_mbox.x"); println!("cargo:rustc-link-arg-bins=-Tdefmt.x"); + + Ok(()) } diff --git a/examples/stm32wb/memory.x b/examples/stm32wb/memory.x deleted file mode 100644 index e1f0530b..00000000 --- a/examples/stm32wb/memory.x +++ /dev/null @@ -1,35 +0,0 @@ -/* - The size of this file must be exactly the same as in other memory_xx.x files. - Memory size for STM32WB55xC with 256K FLASH -*/ - -MEMORY -{ - FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 256K - RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 192K - RAM_SHARED (xrw) : ORIGIN = 0x20030000, LENGTH = 10K -} - -/* - Memory size for STM32WB55xG with 512K FLASH - - MEMORY - { - FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 512K - RAM (xrw) : ORIGIN = 0x20000008, LENGTH = 0x2FFF8 - RAM_SHARED (xrw) : ORIGIN = 0x20030000, LENGTH = 10K - } -*/ - -/* Place stack at the end of SRAM1 */ -_stack_start = ORIGIN(RAM) + LENGTH(RAM); - -/* - * Scatter the mailbox interface memory sections in shared memory - */ -SECTIONS { - TL_REF_TABLE (NOLOAD) : { *(TL_REF_TABLE) } >RAM_SHARED - - MB_MEM1 (NOLOAD) : { *(MB_MEM1) } >RAM_SHARED - MB_MEM2 (NOLOAD) : { _sMB_MEM2 = . ; *(MB_MEM2) ; _eMB_MEM2 = . ; } >RAM_SHARED -} From 7e501855fc2ee98bef6be56244c4587610dbdc32 Mon Sep 17 00:00:00 2001 From: xoviat Date: Sat, 27 May 2023 15:05:07 -0500 Subject: [PATCH 142/178] stm32/ipcc: move into tl_mbox --- embassy-stm32/src/lib.rs | 2 -- embassy-stm32/src/{ => tl_mbox}/ipcc.rs | 40 ++++++++++++------------- 2 files changed, 20 insertions(+), 22 deletions(-) rename embassy-stm32/src/{ => tl_mbox}/ipcc.rs (75%) diff --git a/embassy-stm32/src/lib.rs b/embassy-stm32/src/lib.rs index c9df5c1b..b9d7a15c 100644 --- a/embassy-stm32/src/lib.rs +++ b/embassy-stm32/src/lib.rs @@ -41,8 +41,6 @@ pub mod crc; pub mod flash; #[cfg(all(spi_v1, rcc_f4))] pub mod i2s; -#[cfg(stm32wb)] -pub mod ipcc; pub mod pwm; #[cfg(quadspi)] pub mod qspi; diff --git a/embassy-stm32/src/ipcc.rs b/embassy-stm32/src/tl_mbox/ipcc.rs similarity index 75% rename from embassy-stm32/src/ipcc.rs rename to embassy-stm32/src/tl_mbox/ipcc.rs index ea33b32c..d1ac731e 100644 --- a/embassy-stm32/src/ipcc.rs +++ b/embassy-stm32/src/tl_mbox/ipcc.rs @@ -1,4 +1,4 @@ -use crate::ipcc::sealed::Instance; +use self::sealed::Instance; use crate::peripherals::IPCC; use crate::rcc::sealed::RccPeripheral; @@ -20,17 +20,17 @@ pub enum IpccChannel { Channel6 = 5, } -pub(crate) mod sealed { +pub mod sealed { pub trait Instance: crate::rcc::RccPeripheral { fn regs() -> crate::pac::ipcc::Ipcc; fn set_cpu2(enabled: bool); } } -pub(crate) struct Ipcc; +pub struct Ipcc; impl Ipcc { - pub(crate) fn init(_config: Config) { + pub fn enable(_config: Config) { IPCC::enable(); IPCC::reset(); IPCC::set_cpu2(true); @@ -47,14 +47,14 @@ impl Ipcc { } } - pub(crate) fn c1_set_rx_channel(channel: IpccChannel, enabled: bool) { + pub fn c1_set_rx_channel(channel: IpccChannel, enabled: bool) { let regs = IPCC::regs(); // If bit is set to 1 then interrupt is disabled unsafe { regs.cpu(0).mr().modify(|w| w.set_chom(channel as usize, !enabled)) } } - pub(crate) fn c1_get_rx_channel(channel: IpccChannel) -> bool { + pub fn c1_get_rx_channel(channel: IpccChannel) -> bool { let regs = IPCC::regs(); // If bit is set to 1 then interrupt is disabled @@ -62,7 +62,7 @@ impl Ipcc { } #[allow(dead_code)] - pub(crate) fn c2_set_rx_channel(channel: IpccChannel, enabled: bool) { + pub fn c2_set_rx_channel(channel: IpccChannel, enabled: bool) { let regs = IPCC::regs(); // If bit is set to 1 then interrupt is disabled @@ -70,21 +70,21 @@ impl Ipcc { } #[allow(dead_code)] - pub(crate) fn c2_get_rx_channel(channel: IpccChannel) -> bool { + pub fn c2_get_rx_channel(channel: IpccChannel) -> bool { let regs = IPCC::regs(); // If bit is set to 1 then interrupt is disabled unsafe { !regs.cpu(1).mr().read().chom(channel as usize) } } - pub(crate) fn c1_set_tx_channel(channel: IpccChannel, enabled: bool) { + pub fn c1_set_tx_channel(channel: IpccChannel, enabled: bool) { let regs = IPCC::regs(); // If bit is set to 1 then interrupt is disabled unsafe { regs.cpu(0).mr().modify(|w| w.set_chfm(channel as usize, !enabled)) } } - pub(crate) fn c1_get_tx_channel(channel: IpccChannel) -> bool { + pub fn c1_get_tx_channel(channel: IpccChannel) -> bool { let regs = IPCC::regs(); // If bit is set to 1 then interrupt is disabled @@ -92,7 +92,7 @@ impl Ipcc { } #[allow(dead_code)] - pub(crate) fn c2_set_tx_channel(channel: IpccChannel, enabled: bool) { + pub fn c2_set_tx_channel(channel: IpccChannel, enabled: bool) { let regs = IPCC::regs(); // If bit is set to 1 then interrupt is disabled @@ -100,7 +100,7 @@ impl Ipcc { } #[allow(dead_code)] - pub(crate) fn c2_get_tx_channel(channel: IpccChannel) -> bool { + pub fn c2_get_tx_channel(channel: IpccChannel) -> bool { let regs = IPCC::regs(); // If bit is set to 1 then interrupt is disabled @@ -108,7 +108,7 @@ impl Ipcc { } /// clears IPCC receive channel status for CPU1 - pub(crate) fn c1_clear_flag_channel(channel: IpccChannel) { + pub fn c1_clear_flag_channel(channel: IpccChannel) { let regs = IPCC::regs(); unsafe { regs.cpu(0).scr().write(|w| w.set_chc(channel as usize, true)) } @@ -116,42 +116,42 @@ impl Ipcc { #[allow(dead_code)] /// clears IPCC receive channel status for CPU2 - pub(crate) fn c2_clear_flag_channel(channel: IpccChannel) { + pub fn c2_clear_flag_channel(channel: IpccChannel) { let regs = IPCC::regs(); unsafe { regs.cpu(1).scr().write(|w| w.set_chc(channel as usize, true)) } } - pub(crate) fn c1_set_flag_channel(channel: IpccChannel) { + pub fn c1_set_flag_channel(channel: IpccChannel) { let regs = IPCC::regs(); unsafe { regs.cpu(0).scr().write(|w| w.set_chs(channel as usize, true)) } } #[allow(dead_code)] - pub(crate) fn c2_set_flag_channel(channel: IpccChannel) { + pub fn c2_set_flag_channel(channel: IpccChannel) { let regs = IPCC::regs(); unsafe { regs.cpu(1).scr().write(|w| w.set_chs(channel as usize, true)) } } - pub(crate) fn c1_is_active_flag(channel: IpccChannel) -> bool { + pub fn c1_is_active_flag(channel: IpccChannel) -> bool { let regs = IPCC::regs(); unsafe { regs.cpu(0).sr().read().chf(channel as usize) } } - pub(crate) fn c2_is_active_flag(channel: IpccChannel) -> bool { + pub fn c2_is_active_flag(channel: IpccChannel) -> bool { let regs = IPCC::regs(); unsafe { regs.cpu(1).sr().read().chf(channel as usize) } } - pub(crate) fn is_tx_pending(channel: IpccChannel) -> bool { + pub fn is_tx_pending(channel: IpccChannel) -> bool { !Self::c1_is_active_flag(channel) && Self::c1_get_tx_channel(channel) } - pub(crate) fn is_rx_pending(channel: IpccChannel) -> bool { + pub fn is_rx_pending(channel: IpccChannel) -> bool { Self::c2_is_active_flag(channel) && Self::c1_get_rx_channel(channel) } } From 37e104a6b380c3c7ec20346a44b11050476a6116 Mon Sep 17 00:00:00 2001 From: xoviat Date: Sat, 27 May 2023 15:05:23 -0500 Subject: [PATCH 143/178] stm32/ipcc: refactor tl_mbox --- embassy-stm32/src/tl_mbox/ble.rs | 14 +++----- embassy-stm32/src/tl_mbox/channels.rs | 4 +-- embassy-stm32/src/tl_mbox/evt.rs | 4 +-- embassy-stm32/src/tl_mbox/mm.rs | 10 ++---- embassy-stm32/src/tl_mbox/mod.rs | 46 +++++++++++++++++++-------- embassy-stm32/src/tl_mbox/shci.rs | 2 +- embassy-stm32/src/tl_mbox/sys.rs | 16 ++++------ 7 files changed, 51 insertions(+), 45 deletions(-) diff --git a/embassy-stm32/src/tl_mbox/ble.rs b/embassy-stm32/src/tl_mbox/ble.rs index 5cc0bb20..06237799 100644 --- a/embassy-stm32/src/tl_mbox/ble.rs +++ b/embassy-stm32/src/tl_mbox/ble.rs @@ -1,5 +1,3 @@ -use core::mem::MaybeUninit; - use embassy_futures::block_on; use super::cmd::CmdSerial; @@ -10,17 +8,17 @@ use super::{ channels, BleTable, BLE_CMD_BUFFER, CS_BUFFER, EVT_QUEUE, HCI_ACL_DATA_BUFFER, TL_BLE_TABLE, TL_CHANNEL, TL_REF_TABLE, }; -use crate::ipcc::Ipcc; use crate::tl_mbox::cmd::CmdPacket; +use crate::tl_mbox::ipcc::Ipcc; pub struct Ble; impl Ble { - pub(crate) fn new() -> Self { + pub fn enable() { unsafe { LinkedListNode::init_head(EVT_QUEUE.as_mut_ptr()); - TL_BLE_TABLE = MaybeUninit::new(BleTable { + TL_BLE_TABLE.as_mut_ptr().write_volatile(BleTable { pcmd_buffer: BLE_CMD_BUFFER.as_mut_ptr().cast(), pcs_buffer: CS_BUFFER.as_mut_ptr().cast(), pevt_queue: EVT_QUEUE.as_ptr().cast(), @@ -29,11 +27,9 @@ impl Ble { } Ipcc::c1_set_rx_channel(channels::cpu2::IPCC_BLE_EVENT_CHANNEL, true); - - Ble } - pub(crate) fn evt_handler() { + pub fn evt_handler() { unsafe { let mut node_ptr = core::ptr::null_mut(); let node_ptr_ptr: *mut _ = &mut node_ptr; @@ -51,7 +47,7 @@ impl Ble { Ipcc::c1_clear_flag_channel(channels::cpu2::IPCC_BLE_EVENT_CHANNEL); } - pub(crate) fn send_cmd(buf: &[u8]) { + pub fn send_cmd(buf: &[u8]) { unsafe { let pcmd_buffer: *mut CmdPacket = (*TL_REF_TABLE.assume_init().ble_table).pcmd_buffer; let pcmd_serial: *mut CmdSerial = &mut (*pcmd_buffer).cmd_serial; diff --git a/embassy-stm32/src/tl_mbox/channels.rs b/embassy-stm32/src/tl_mbox/channels.rs index aaa6ce17..25a065ba 100644 --- a/embassy-stm32/src/tl_mbox/channels.rs +++ b/embassy-stm32/src/tl_mbox/channels.rs @@ -50,7 +50,7 @@ //! pub mod cpu1 { - use crate::ipcc::IpccChannel; + use crate::tl_mbox::ipcc::IpccChannel; // Not used currently but reserved pub const IPCC_BLE_CMD_CHANNEL: IpccChannel = IpccChannel::Channel1; @@ -75,7 +75,7 @@ pub mod cpu1 { } pub mod cpu2 { - use crate::ipcc::IpccChannel; + use crate::tl_mbox::ipcc::IpccChannel; pub const IPCC_BLE_EVENT_CHANNEL: IpccChannel = IpccChannel::Channel1; pub const IPCC_SYSTEM_EVENT_CHANNEL: IpccChannel = IpccChannel::Channel2; diff --git a/embassy-stm32/src/tl_mbox/evt.rs b/embassy-stm32/src/tl_mbox/evt.rs index 04feb3e6..47a8b72f 100644 --- a/embassy-stm32/src/tl_mbox/evt.rs +++ b/embassy-stm32/src/tl_mbox/evt.rs @@ -3,7 +3,7 @@ use core::mem::MaybeUninit; use super::cmd::{AclDataPacket, AclDataSerial}; use super::consts::TlPacketType; use super::{PacketHeader, TL_EVT_HEADER_SIZE}; -use crate::tl_mbox::mm; +use crate::tl_mbox::mm::MemoryManager; /// the payload of [`Evt`] for a command status event #[derive(Copy, Clone)] @@ -131,6 +131,6 @@ impl EvtBox { impl Drop for EvtBox { fn drop(&mut self) { - mm::MemoryManager::evt_drop(self.ptr); + MemoryManager::evt_drop(self.ptr); } } diff --git a/embassy-stm32/src/tl_mbox/mm.rs b/embassy-stm32/src/tl_mbox/mm.rs index 6e0818cf..e28a6aa0 100644 --- a/embassy-stm32/src/tl_mbox/mm.rs +++ b/embassy-stm32/src/tl_mbox/mm.rs @@ -1,22 +1,20 @@ -use core::mem::MaybeUninit; - use super::evt::EvtPacket; use super::unsafe_linked_list::LinkedListNode; use super::{ channels, MemManagerTable, BLE_SPARE_EVT_BUF, EVT_POOL, FREE_BUFF_QUEUE, LOCAL_FREE_BUF_QUEUE, POOL_SIZE, SYS_SPARE_EVT_BUF, TL_MEM_MANAGER_TABLE, TL_REF_TABLE, }; -use crate::ipcc::Ipcc; +use crate::tl_mbox::ipcc::Ipcc; pub struct MemoryManager; impl MemoryManager { - pub fn new() -> Self { + pub fn enable() { unsafe { LinkedListNode::init_head(FREE_BUFF_QUEUE.as_mut_ptr()); LinkedListNode::init_head(LOCAL_FREE_BUF_QUEUE.as_mut_ptr()); - TL_MEM_MANAGER_TABLE = MaybeUninit::new(MemManagerTable { + TL_MEM_MANAGER_TABLE.as_mut_ptr().write_volatile(MemManagerTable { spare_ble_buffer: BLE_SPARE_EVT_BUF.as_ptr().cast(), spare_sys_buffer: SYS_SPARE_EVT_BUF.as_ptr().cast(), ble_pool: EVT_POOL.as_ptr().cast(), @@ -26,8 +24,6 @@ impl MemoryManager { traces_pool_size: 0, }); } - - MemoryManager } pub fn evt_handler() { diff --git a/embassy-stm32/src/tl_mbox/mod.rs b/embassy-stm32/src/tl_mbox/mod.rs index 1c927efa..616f7dc5 100644 --- a/embassy-stm32/src/tl_mbox/mod.rs +++ b/embassy-stm32/src/tl_mbox/mod.rs @@ -1,7 +1,9 @@ use core::mem::MaybeUninit; +use atomic_polyfill::{compiler_fence, Ordering}; use bit_field::BitField; use embassy_cortex_m::interrupt::{Interrupt, InterruptExt}; +use embassy_hal_common::{into_ref, Peripheral, PeripheralRef}; use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; use embassy_sync::channel::Channel; @@ -13,13 +15,16 @@ use self::shci::{shci_ble_init, ShciBleInitCmdParam}; use self::sys::Sys; use self::unsafe_linked_list::LinkedListNode; use crate::interrupt; -use crate::ipcc::{Config, Ipcc}; +use crate::peripherals::IPCC; +pub use crate::tl_mbox::ipcc::Config; +use crate::tl_mbox::ipcc::Ipcc; mod ble; mod channels; mod cmd; mod consts; mod evt; +mod ipcc; mod mm; mod shci; mod sys; @@ -60,6 +65,8 @@ pub struct ReceiveInterruptHandler {} impl interrupt::Handler for ReceiveInterruptHandler { unsafe fn on_interrupt() { + // info!("ipcc rx interrupt"); + if Ipcc::is_rx_pending(channels::cpu2::IPCC_SYSTEM_EVENT_CHANNEL) { sys::Sys::evt_handler(); } else if Ipcc::is_rx_pending(channels::cpu2::IPCC_BLE_EVENT_CHANNEL) { @@ -74,6 +81,8 @@ pub struct TransmitInterruptHandler {} impl interrupt::Handler for TransmitInterruptHandler { unsafe fn on_interrupt() { + // info!("ipcc tx interrupt"); + if Ipcc::is_tx_pending(channels::cpu1::IPCC_SYSTEM_CMD_RSP_CHANNEL) { // TODO: handle this case let _ = sys::Sys::cmd_evt_handler(); @@ -307,21 +316,24 @@ static mut HCI_ACL_DATA_BUFFER: MaybeUninit<[u8; TL_PACKET_HEADER_SIZE + 5 + 251 // TODO: get a better size, this is a placeholder pub(crate) static TL_CHANNEL: Channel = Channel::new(); -pub struct TlMbox { - _sys: Sys, - _ble: Ble, - _mm: MemoryManager, +pub struct TlMbox<'d> { + _ipcc: PeripheralRef<'d, IPCC>, } -impl TlMbox { +impl<'d> TlMbox<'d> { /// initializes low-level transport between CPU1 and BLE stack on CPU2 - pub fn init( + pub fn new( + ipcc: impl Peripheral

+ 'd, _irqs: impl interrupt::Binding + interrupt::Binding, config: Config, - ) -> TlMbox { + ) -> Self { + into_ref!(ipcc); + unsafe { - TL_REF_TABLE = MaybeUninit::new(RefTable { + compiler_fence(Ordering::AcqRel); + + TL_REF_TABLE.as_mut_ptr().write_volatile(RefTable { device_info_table: TL_DEVICE_INFO_TABLE.as_ptr(), ble_table: TL_BLE_TABLE.as_ptr(), thread_table: TL_THREAD_TABLE.as_ptr(), @@ -334,6 +346,10 @@ impl TlMbox { ble_lld_table: TL_BLE_LLD_TABLE.as_ptr(), }); + // info!("TL_REF_TABLE addr: {:x}", TL_REF_TABLE.as_ptr() as usize); + + compiler_fence(Ordering::AcqRel); + TL_SYS_TABLE = MaybeUninit::zeroed(); TL_DEVICE_INFO_TABLE = MaybeUninit::zeroed(); TL_BLE_TABLE = MaybeUninit::zeroed(); @@ -352,13 +368,15 @@ impl TlMbox { CS_BUFFER = MaybeUninit::zeroed(); BLE_CMD_BUFFER = MaybeUninit::zeroed(); HCI_ACL_DATA_BUFFER = MaybeUninit::zeroed(); + + compiler_fence(Ordering::AcqRel); } - Ipcc::init(config); + Ipcc::enable(config); - let _sys = Sys::new(); - let _ble = Ble::new(); - let _mm = MemoryManager::new(); + Sys::enable(); + Ble::enable(); + MemoryManager::enable(); // enable interrupts unsafe { crate::interrupt::IPCC_C1_RX::steal() }.unpend(); @@ -367,7 +385,7 @@ impl TlMbox { unsafe { crate::interrupt::IPCC_C1_RX::steal() }.enable(); unsafe { crate::interrupt::IPCC_C1_TX::steal() }.enable(); - TlMbox { _sys, _ble, _mm } + Self { _ipcc: ipcc } } pub fn wireless_fw_info(&self) -> Option { diff --git a/embassy-stm32/src/tl_mbox/shci.rs b/embassy-stm32/src/tl_mbox/shci.rs index 3d7e994a..6b5b2dd1 100644 --- a/embassy-stm32/src/tl_mbox/shci.rs +++ b/embassy-stm32/src/tl_mbox/shci.rs @@ -3,7 +3,7 @@ use super::cmd::CmdPacket; use super::consts::TlPacketType; use super::{channels, TL_CS_EVT_SIZE, TL_EVT_HEADER_SIZE, TL_PACKET_HEADER_SIZE, TL_SYS_TABLE}; -use crate::ipcc::Ipcc; +use crate::tl_mbox::ipcc::Ipcc; const SCHI_OPCODE_BLE_INIT: u16 = 0xfc66; pub const TL_BLE_EVT_CS_PACKET_SIZE: usize = TL_EVT_HEADER_SIZE + TL_CS_EVT_SIZE; diff --git a/embassy-stm32/src/tl_mbox/sys.rs b/embassy-stm32/src/tl_mbox/sys.rs index 79e25714..9685fb92 100644 --- a/embassy-stm32/src/tl_mbox/sys.rs +++ b/embassy-stm32/src/tl_mbox/sys.rs @@ -1,5 +1,3 @@ -use core::mem::MaybeUninit; - use embassy_futures::block_on; use super::cmd::{CmdPacket, CmdSerial}; @@ -7,27 +5,25 @@ use super::consts::TlPacketType; use super::evt::{CcEvt, EvtBox, EvtSerial}; use super::unsafe_linked_list::LinkedListNode; use super::{channels, SysTable, SYSTEM_EVT_QUEUE, SYS_CMD_BUF, TL_CHANNEL, TL_REF_TABLE, TL_SYS_TABLE}; -use crate::ipcc::Ipcc; +use crate::tl_mbox::ipcc::Ipcc; pub struct Sys; impl Sys { - pub(crate) fn new() -> Self { + pub fn enable() { unsafe { LinkedListNode::init_head(SYSTEM_EVT_QUEUE.as_mut_ptr()); - TL_SYS_TABLE = MaybeUninit::new(SysTable { + TL_SYS_TABLE.as_mut_ptr().write_volatile(SysTable { pcmd_buffer: SYS_CMD_BUF.as_mut_ptr(), sys_queue: SYSTEM_EVT_QUEUE.as_ptr(), }); } Ipcc::c1_set_rx_channel(channels::cpu2::IPCC_SYSTEM_EVENT_CHANNEL, true); - - Sys } - pub(crate) fn evt_handler() { + pub fn evt_handler() { unsafe { let mut node_ptr = core::ptr::null_mut(); let node_ptr_ptr: *mut _ = &mut node_ptr; @@ -46,7 +42,7 @@ impl Sys { Ipcc::c1_clear_flag_channel(channels::cpu2::IPCC_SYSTEM_EVENT_CHANNEL); } - pub(crate) fn cmd_evt_handler() -> CcEvt { + pub fn cmd_evt_handler() -> CcEvt { Ipcc::c1_set_tx_channel(channels::cpu1::IPCC_SYSTEM_CMD_RSP_CHANNEL, false); // ST's command response data structure is really convoluted. @@ -68,7 +64,7 @@ impl Sys { } #[allow(dead_code)] - pub(crate) fn send_cmd(buf: &[u8]) { + pub fn send_cmd(buf: &[u8]) { unsafe { // TODO: check this let cmd_buffer = &mut *(*TL_REF_TABLE.assume_init().sys_table).pcmd_buffer; From 09d52638b551a37c8b032ffb6daaa1abd2efa231 Mon Sep 17 00:00:00 2001 From: xoviat Date: Sat, 27 May 2023 15:05:50 -0500 Subject: [PATCH 144/178] stm32/ipcc: refactor examples and tests --- examples/stm32wb/src/bin/tl_mbox.rs | 14 ++++++++------ examples/stm32wb/src/bin/tl_mbox_tx_rx.rs | 10 +++++----- tests/stm32/.cargo/config.toml | 6 ++++-- tests/stm32/Cargo.toml | 6 +++--- tests/stm32/build.rs | 13 +++++++++---- tests/stm32/src/bin/{ble.rs => tl_mbox.rs} | 7 +++---- 6 files changed, 32 insertions(+), 24 deletions(-) rename tests/stm32/src/bin/{ble.rs => tl_mbox.rs} (89%) diff --git a/examples/stm32wb/src/bin/tl_mbox.rs b/examples/stm32wb/src/bin/tl_mbox.rs index 18d93a27..8f4e70af 100644 --- a/examples/stm32wb/src/bin/tl_mbox.rs +++ b/examples/stm32wb/src/bin/tl_mbox.rs @@ -4,8 +4,7 @@ use defmt::*; use embassy_executor::Spawner; -use embassy_stm32::ipcc::Config; -use embassy_stm32::tl_mbox::TlMbox; +use embassy_stm32::tl_mbox::{Config, TlMbox}; use embassy_stm32::{bind_interrupts, tl_mbox}; use embassy_time::{Duration, Timer}; use {defmt_rtt as _, panic_probe as _}; @@ -41,16 +40,16 @@ async fn main(_spawner: Spawner) { 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()); + let p = embassy_stm32::init(Default::default()); info!("Hello World!"); let config = Config::default(); - let mbox = TlMbox::init(Irqs, config); + let mbox = TlMbox::new(p.IPCC, Irqs, config); loop { let wireless_fw_info = mbox.wireless_fw_info(); match wireless_fw_info { - None => error!("not yet initialized"), + None => info!("not yet initialized"), Some(fw_info) => { let version_major = fw_info.version_major(); let version_minor = fw_info.version_minor(); @@ -68,6 +67,9 @@ async fn main(_spawner: Spawner) { } } - Timer::after(Duration::from_millis(500)).await; + Timer::after(Duration::from_millis(50)).await; } + + info!("Test OK"); + cortex_m::asm::bkpt(); } diff --git a/examples/stm32wb/src/bin/tl_mbox_tx_rx.rs b/examples/stm32wb/src/bin/tl_mbox_tx_rx.rs index 41c450a5..1724d946 100644 --- a/examples/stm32wb/src/bin/tl_mbox_tx_rx.rs +++ b/examples/stm32wb/src/bin/tl_mbox_tx_rx.rs @@ -4,8 +4,7 @@ use defmt::*; use embassy_executor::Spawner; -use embassy_stm32::ipcc::Config; -use embassy_stm32::tl_mbox::TlMbox; +use embassy_stm32::tl_mbox::{Config, TlMbox}; use embassy_stm32::{bind_interrupts, tl_mbox}; use {defmt_rtt as _, panic_probe as _}; @@ -40,11 +39,11 @@ async fn main(_spawner: Spawner) { 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()); + let p = embassy_stm32::init(Default::default()); info!("Hello World!"); let config = Config::default(); - let mbox = TlMbox::init(Irqs, config); + let mbox = TlMbox::new(p.IPCC, Irqs, config); info!("waiting for coprocessor to boot"); let event_box = mbox.read().await; @@ -94,5 +93,6 @@ async fn main(_spawner: Spawner) { payload[3..] ); - loop {} + info!("Test OK"); + cortex_m::asm::bkpt(); } diff --git a/tests/stm32/.cargo/config.toml b/tests/stm32/.cargo/config.toml index 29c4799a..48588f0b 100644 --- a/tests/stm32/.cargo/config.toml +++ b/tests/stm32/.cargo/config.toml @@ -3,8 +3,10 @@ build-std = ["core"] build-std-features = ["panic_immediate_abort"] [target.'cfg(all(target_arch = "arm", target_os = "none"))'] -runner = "teleprobe client run --target bluepill-stm32f103c8 --elf" +#runner = "teleprobe client run --target bluepill-stm32f103c8 --elf" #runner = "teleprobe local run --chip STM32F103C8 --elf" +#runner = "teleprobe local run --chip STM32WB55RG --elf" +runner = "probe-run --chip STM32WB55RG" rustflags = [ # Code-size optimizations. @@ -14,7 +16,7 @@ rustflags = [ ] [build] -target = "thumbv7m-none-eabi" +target = "thumbv7em-none-eabi" [env] DEFMT_LOG = "trace" diff --git a/tests/stm32/Cargo.toml b/tests/stm32/Cargo.toml index bd8d90ab..23b5f9ca 100644 --- a/tests/stm32/Cargo.toml +++ b/tests/stm32/Cargo.toml @@ -12,7 +12,7 @@ stm32g071rb = ["embassy-stm32/stm32g071rb", "not-gpdma"] # Nucleo stm32c031c6 = ["embassy-stm32/stm32c031c6", "not-gpdma"] # Nucleo stm32g491re = ["embassy-stm32/stm32g491re", "not-gpdma"] # Nucleo stm32h755zi = ["embassy-stm32/stm32h755zi-cm7", "not-gpdma"] # Nucleo -stm32wb55rg = ["embassy-stm32/stm32wb55rg", "not-gpdma"] # Nucleo +stm32wb55rg = ["embassy-stm32/stm32wb55rg", "not-gpdma", "ble"] # Nucleo stm32h563zi = ["embassy-stm32/stm32h563zi"] # Nucleo stm32u585ai = ["embassy-stm32/stm32u585ai"] # IoT board @@ -45,8 +45,8 @@ chrono = { version = "^0.4", default-features = false, optional = true} # BEGIN TESTS # Generated by gen_test.py. DO NOT EDIT. [[bin]] -name = "ble" -path = "src/bin/ble.rs" +name = "tl_mbox" +path = "src/bin/tl_mbox.rs" required-features = [ "ble",] [[bin]] diff --git a/tests/stm32/build.rs b/tests/stm32/build.rs index ca76b70b..b4583147 100644 --- a/tests/stm32/build.rs +++ b/tests/stm32/build.rs @@ -9,17 +9,22 @@ fn main() -> Result<(), Box> { println!("cargo:rustc-link-arg-bins=--nmagic"); // too little RAM to run from RAM. - if cfg!(any(feature = "stm32f103c8", feature = "stm32c031c6")) { + if cfg!(any( + feature = "stm32f103c8", + feature = "stm32c031c6", + feature = "stm32wb55rg" + )) { println!("cargo:rustc-link-arg-bins=-Tlink.x"); println!("cargo:rerun-if-changed=link.x"); - } else if cfg!(feature = "stm32wb55rg") { - println!("cargo:rustc-link-arg-bins=-Tlink.x"); - fs::write(out.join("memory.x"), include_bytes!("memory_ble.x")).unwrap(); } else { println!("cargo:rustc-link-arg-bins=-Tlink_ram.x"); println!("cargo:rerun-if-changed=link_ram.x"); } + if cfg!(feature = "stm32wb55rg") { + println!("cargo:rustc-link-arg-bins=-Ttl_mbox.x"); + } + println!("cargo:rustc-link-arg-bins=-Tdefmt.x"); Ok(()) diff --git a/tests/stm32/src/bin/ble.rs b/tests/stm32/src/bin/tl_mbox.rs similarity index 89% rename from tests/stm32/src/bin/ble.rs rename to tests/stm32/src/bin/tl_mbox.rs index db5cc611..626e7ac6 100644 --- a/tests/stm32/src/bin/ble.rs +++ b/tests/stm32/src/bin/tl_mbox.rs @@ -7,8 +7,7 @@ #[path = "../example_common.rs"] mod example_common; use embassy_executor::Spawner; -use embassy_stm32::ipcc::Config; -use embassy_stm32::tl_mbox::TlMbox; +use embassy_stm32::tl_mbox::{Config, TlMbox}; use embassy_stm32::{bind_interrupts, tl_mbox}; use embassy_time::{Duration, Timer}; use example_common::*; @@ -20,11 +19,11 @@ bind_interrupts!(struct Irqs{ #[embassy_executor::main] async fn main(_spawner: Spawner) { - let _p = embassy_stm32::init(config()); + let p = embassy_stm32::init(config()); info!("Hello World!"); let config = Config::default(); - let mbox = TlMbox::init(Irqs, config); + let mbox = TlMbox::new(p.IPCC, Irqs, config); loop { let wireless_fw_info = mbox.wireless_fw_info(); From 5d7301e510a0882d004cdaee337280816d490afd Mon Sep 17 00:00:00 2001 From: xoviat Date: Sat, 27 May 2023 15:08:30 -0500 Subject: [PATCH 145/178] tests/stm32: revert cfg changes --- tests/stm32/.cargo/config.toml | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/tests/stm32/.cargo/config.toml b/tests/stm32/.cargo/config.toml index 48588f0b..426d6e67 100644 --- a/tests/stm32/.cargo/config.toml +++ b/tests/stm32/.cargo/config.toml @@ -3,10 +3,8 @@ build-std = ["core"] build-std-features = ["panic_immediate_abort"] [target.'cfg(all(target_arch = "arm", target_os = "none"))'] -#runner = "teleprobe client run --target bluepill-stm32f103c8 --elf" +runner = "teleprobe client run --target bluepill-stm32f103c8 --elf" #runner = "teleprobe local run --chip STM32F103C8 --elf" -#runner = "teleprobe local run --chip STM32WB55RG --elf" -runner = "probe-run --chip STM32WB55RG" rustflags = [ # Code-size optimizations. @@ -16,7 +14,7 @@ rustflags = [ ] [build] -target = "thumbv7em-none-eabi" +target = "thumbv7m-none-eabi" [env] -DEFMT_LOG = "trace" +DEFMT_LOG = "trace" \ No newline at end of file From bd6a1d38d208f770d15a70a89112fc74046a96ef Mon Sep 17 00:00:00 2001 From: xoviat Date: Mon, 29 May 2023 09:16:50 -0500 Subject: [PATCH 146/178] stm32/tests: disable sdmmc test for now --- tests/stm32/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/stm32/Cargo.toml b/tests/stm32/Cargo.toml index bd8d90ab..6e7a4a57 100644 --- a/tests/stm32/Cargo.toml +++ b/tests/stm32/Cargo.toml @@ -7,7 +7,7 @@ autobins = false [features] stm32f103c8 = ["embassy-stm32/stm32f103c8", "not-gpdma"] # Blue Pill -stm32f429zi = ["embassy-stm32/stm32f429zi", "sdmmc", "chrono", "not-gpdma"] # Nucleo +stm32f429zi = ["embassy-stm32/stm32f429zi", "chrono", "not-gpdma"] # Nucleo "sdmmc" stm32g071rb = ["embassy-stm32/stm32g071rb", "not-gpdma"] # Nucleo stm32c031c6 = ["embassy-stm32/stm32c031c6", "not-gpdma"] # Nucleo stm32g491re = ["embassy-stm32/stm32g491re", "not-gpdma"] # Nucleo From 7b2a39a6fb62e0a1483b31e2d24dec71013558e7 Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Fri, 26 May 2023 23:05:18 +0200 Subject: [PATCH 147/178] Switch to Bender for CI. --- .github/ci/rust.sh | 21 ++++++++++ .github/workflows/rust.yml | 80 -------------------------------------- ci.sh | 1 - 3 files changed, 21 insertions(+), 81 deletions(-) create mode 100755 .github/ci/rust.sh delete mode 100644 .github/workflows/rust.yml diff --git a/.github/ci/rust.sh b/.github/ci/rust.sh new file mode 100755 index 00000000..af7f336c --- /dev/null +++ b/.github/ci/rust.sh @@ -0,0 +1,21 @@ +#!/bin/bash +## on push branch=main +## on push branch~=gh-readonly-queue/main/.* +## on pull_request + +set -euo pipefail + +echo Hello World! + +export RUSTUP_HOME=/ci/cache/rustup +export CARGO_HOME=/ci/cache/cargo +export CARGO_TARGET_DIR=/ci/cache/target +if [ -f /ci/secrets/teleprobe-token.txt ]; then + echo Got teleprobe token! + export TELEPROBE_TOKEN=$(cat /ci/secrets/teleprobe-token.txt) +fi + +hashtime restore /ci/cache/filetime.json || true +hashtime save /ci/cache/filetime.json + +./ci.sh diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml deleted file mode 100644 index 0cbca31b..00000000 --- a/.github/workflows/rust.yml +++ /dev/null @@ -1,80 +0,0 @@ -name: Rust - -on: - push: - branches: [staging, trying, master] - pull_request: - branches: [master] - -env: - CARGO_TERM_COLOR: always - -jobs: - all: - runs-on: ubuntu-latest - needs: [build-nightly, build-stable, test] - steps: - - name: Done - run: exit 0 - build-nightly: - runs-on: ubuntu-latest - permissions: - id-token: write - contents: read - steps: - - uses: actions/checkout@v3 - with: - submodules: true - - name: Cache multiple paths - uses: actions/cache@v3 - with: - path: | - ~/.cargo/bin/ - ~/.cargo/registry/index/ - ~/.cargo/registry/cache/ - ~/.cargo/git/db/ - target_ci - key: rust3-${{ runner.os }}-${{ hashFiles('rust-toolchain.toml') }} - - name: build - env: - TELEPROBE_TOKEN: ${{ secrets.TELEPROBE_TOKEN }} - run: | - curl -L -o /usr/local/bin/cargo-batch https://github.com/embassy-rs/cargo-batch/releases/download/batch-0.3.0/cargo-batch - chmod +x /usr/local/bin/cargo-batch - ./ci.sh - rm -rf target_ci/*{,/release}/{build,deps,.fingerprint}/{lib,}{embassy,stm32}* - build-stable: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - with: - submodules: true - - name: Cache multiple paths - uses: actions/cache@v3 - with: - path: | - ~/.cargo/bin/ - ~/.cargo/registry/index/ - ~/.cargo/registry/cache/ - ~/.cargo/git/db/ - target_ci_stable - key: rust-stable-${{ runner.os }}-${{ hashFiles('rust-toolchain.toml') }} - - name: build - run: | - curl -L -o /usr/local/bin/cargo-batch https://github.com/embassy-rs/cargo-batch/releases/download/batch-0.3.0/cargo-batch - chmod +x /usr/local/bin/cargo-batch - ./ci_stable.sh - rm -rf target_ci_stable/*{,/release}/{build,deps,.fingerprint}/{lib,}{embassy,stm32}* - - test: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - - name: Test boot - working-directory: ./embassy-boot/boot - run: cargo test && cargo test --features nightly && cargo test --features "ed25519-dalek,nightly" && cargo test --features "ed25519-salty,nightly" - - - name: Test sync - working-directory: ./embassy-sync - run: cargo test diff --git a/ci.sh b/ci.sh index 6d906f5f..11c56932 100755 --- a/ci.sh +++ b/ci.sh @@ -2,7 +2,6 @@ set -euo pipefail -export CARGO_TARGET_DIR=$PWD/target_ci export RUSTFLAGS=-Dwarnings export DEFMT_LOG=trace From 6cb6e575920613c2aefca22c06764a098521cf5b Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Mon, 29 May 2023 18:49:50 +0200 Subject: [PATCH 148/178] CI fixes. --- .github/ci/rust.sh | 1 - .github/workflows/doc.yml | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/ci/rust.sh b/.github/ci/rust.sh index af7f336c..db1fc853 100755 --- a/.github/ci/rust.sh +++ b/.github/ci/rust.sh @@ -1,5 +1,4 @@ #!/bin/bash -## on push branch=main ## on push branch~=gh-readonly-queue/main/.* ## on pull_request diff --git a/.github/workflows/doc.yml b/.github/workflows/doc.yml index b4e225e6..a69a4971 100644 --- a/.github/workflows/doc.yml +++ b/.github/workflows/doc.yml @@ -2,7 +2,7 @@ name: Docs on: push: - branches: [master] + branches: [main] env: BUILDER_THREADS: '1' From 46961cfdf72a3b5d54b241a41d9f2496c6dc6229 Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Mon, 29 May 2023 19:46:28 +0200 Subject: [PATCH 149/178] Fix tests. --- .../src/shared_bus/blocking/i2c.rs | 5 ++--- .../src/shared_bus/blocking/spi.rs | 5 ++--- embassy-hal-common/src/atomic_ring_buffer.rs | 2 -- embassy-nrf/src/time_driver.rs | 2 +- embassy-stm32/src/flash/common.rs | 2 +- embassy-stm32/src/pwm/complementary_pwm.rs | 14 +++++++------- embassy-time/Cargo.toml | 2 +- embassy-time/src/driver.rs | 2 +- embassy-time/src/queue_generic.rs | 15 +++++++++------ embassy-time/src/timer.rs | 1 - 10 files changed, 24 insertions(+), 26 deletions(-) diff --git a/embassy-embedded-hal/src/shared_bus/blocking/i2c.rs b/embassy-embedded-hal/src/shared_bus/blocking/i2c.rs index 1fe520e6..af73df05 100644 --- a/embassy-embedded-hal/src/shared_bus/blocking/i2c.rs +++ b/embassy-embedded-hal/src/shared_bus/blocking/i2c.rs @@ -2,13 +2,12 @@ //! //! # Example (nrf52) //! -//! ```rust +//! ```rust,ignore //! use embassy_embedded_hal::shared_bus::blocking::i2c::I2cDevice; //! use embassy_sync::blocking_mutex::{NoopMutex, raw::NoopRawMutex}; //! //! static I2C_BUS: StaticCell>>> = StaticCell::new(); -//! let irq = interrupt::take!(SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0); -//! let i2c = Twim::new(p.TWISPI0, irq, p.P0_03, p.P0_04, Config::default()); +//! let i2c = Twim::new(p.TWISPI0, Irqs, p.P0_03, p.P0_04, Config::default()); //! let i2c_bus = NoopMutex::new(RefCell::new(i2c)); //! let i2c_bus = I2C_BUS.init(i2c_bus); //! diff --git a/embassy-embedded-hal/src/shared_bus/blocking/spi.rs b/embassy-embedded-hal/src/shared_bus/blocking/spi.rs index 7982ffb6..22e013be 100644 --- a/embassy-embedded-hal/src/shared_bus/blocking/spi.rs +++ b/embassy-embedded-hal/src/shared_bus/blocking/spi.rs @@ -2,13 +2,12 @@ //! //! # Example (nrf52) //! -//! ```rust +//! ```rust,ignore //! use embassy_embedded_hal::shared_bus::blocking::spi::SpiDevice; //! use embassy_sync::blocking_mutex::{NoopMutex, raw::NoopRawMutex}; //! //! static SPI_BUS: StaticCell>>> = StaticCell::new(); -//! let irq = interrupt::take!(SPIM3); -//! let spi = Spim::new_txonly(p.SPI3, irq, p.P0_15, p.P0_18, Config::default()); +//! let spi = Spim::new_txonly(p.SPI3, Irqs, p.P0_15, p.P0_18, Config::default()); //! let spi_bus = NoopMutex::new(RefCell::new(spi)); //! let spi_bus = SPI_BUS.init(spi_bus); //! diff --git a/embassy-hal-common/src/atomic_ring_buffer.rs b/embassy-hal-common/src/atomic_ring_buffer.rs index 0eb39cb3..ea84925c 100644 --- a/embassy-hal-common/src/atomic_ring_buffer.rs +++ b/embassy-hal-common/src/atomic_ring_buffer.rs @@ -458,8 +458,6 @@ mod tests { #[test] fn push_slices() { - init(); - let mut b = [0; 4]; let rb = RingBuffer::new(); unsafe { diff --git a/embassy-nrf/src/time_driver.rs b/embassy-nrf/src/time_driver.rs index bc2c8a3c..c82c238c 100644 --- a/embassy-nrf/src/time_driver.rs +++ b/embassy-nrf/src/time_driver.rs @@ -67,7 +67,7 @@ fn compare_n(n: usize) -> u32 { 1 << (n + 16) } -#[cfg(tests)] +#[cfg(test)] mod test { use super::*; diff --git a/embassy-stm32/src/flash/common.rs b/embassy-stm32/src/flash/common.rs index 6c191290..c6cdc574 100644 --- a/embassy-stm32/src/flash/common.rs +++ b/embassy-stm32/src/flash/common.rs @@ -163,7 +163,7 @@ pub(super) fn get_sector(address: u32, regions: &[&FlashRegion]) -> FlashSector bank_offset = 0; } - if address < region.end() { + if address >= region.base && address < region.end() { let index_in_region = (address - region.base) / region.erase_size; return FlashSector { bank: region.bank, diff --git a/embassy-stm32/src/pwm/complementary_pwm.rs b/embassy-stm32/src/pwm/complementary_pwm.rs index 3f8b43cf..cfb79947 100644 --- a/embassy-stm32/src/pwm/complementary_pwm.rs +++ b/embassy-stm32/src/pwm/complementary_pwm.rs @@ -209,39 +209,39 @@ mod tests { #[test] fn test_compute_dead_time_value() { - struct test_run { + struct TestRun { value: u16, ckd: Ckd, bits: u8, } let fn_results = [ - test_run { + TestRun { value: 1, ckd: Ckd::DIV1, bits: 1, }, - test_run { + TestRun { value: 125, ckd: Ckd::DIV1, bits: 125, }, - test_run { + TestRun { value: 245, ckd: Ckd::DIV1, bits: 64 + 245 / 2, }, - test_run { + TestRun { value: 255, ckd: Ckd::DIV2, bits: 127, }, - test_run { + TestRun { value: 400, ckd: Ckd::DIV1, bits: 32 + (400u16 / 8) as u8, }, - test_run { + TestRun { value: 600, ckd: Ckd::DIV4, bits: 64 + (600u16 / 8) as u8, diff --git a/embassy-time/Cargo.toml b/embassy-time/Cargo.toml index c4edbd38..857da546 100644 --- a/embassy-time/Cargo.toml +++ b/embassy-time/Cargo.toml @@ -169,4 +169,4 @@ wasm-timer = { version = "0.2.5", optional = true } [dev-dependencies] serial_test = "0.9" critical-section = { version = "1.1", features = ["std"] } - +embassy-executor = { version = "0.2.0", path = "../embassy-executor", features = ["nightly"] } diff --git a/embassy-time/src/driver.rs b/embassy-time/src/driver.rs index 5c2ad3b2..d6436369 100644 --- a/embassy-time/src/driver.rs +++ b/embassy-time/src/driver.rs @@ -49,7 +49,7 @@ //! fn set_alarm_callback(&self, alarm: AlarmHandle, callback: fn(*mut ()), ctx: *mut ()) { //! todo!() //! } -//! fn set_alarm(&self, alarm: AlarmHandle, timestamp: u64) { +//! fn set_alarm(&self, alarm: AlarmHandle, timestamp: u64) -> bool { //! todo!() //! } //! } diff --git a/embassy-time/src/queue_generic.rs b/embassy-time/src/queue_generic.rs index 64a8af4b..4795eb2f 100644 --- a/embassy-time/src/queue_generic.rs +++ b/embassy-time/src/queue_generic.rs @@ -183,7 +183,6 @@ mod tests { use serial_test::serial; - use super::InnerQueue; use crate::driver::{AlarmHandle, Driver}; use crate::queue_generic::QUEUE; use crate::Instant; @@ -317,14 +316,18 @@ mod tests { fn setup() { DRIVER.reset(); - - QUEUE.inner.lock(|inner| { - *inner.borrow_mut() = InnerQueue::new(); - }); + critical_section::with(|cs| *QUEUE.inner.borrow_ref_mut(cs) = None); } fn queue_len() -> usize { - QUEUE.inner.lock(|inner| inner.borrow().queue.iter().count()) + critical_section::with(|cs| { + QUEUE + .inner + .borrow_ref(cs) + .as_ref() + .map(|inner| inner.queue.iter().count()) + .unwrap_or(0) + }) } #[test] diff --git a/embassy-time/src/timer.rs b/embassy-time/src/timer.rs index 52620d23..d3d1f9f5 100644 --- a/embassy-time/src/timer.rs +++ b/embassy-time/src/timer.rs @@ -109,7 +109,6 @@ impl Future for Timer { /// # #![feature(type_alias_impl_trait)] /// # /// use embassy_time::{Duration, Ticker}; -/// use futures::StreamExt; /// # fn foo(){} /// /// #[embassy_executor::task] From 421ee4dfbfbbdc007265498d4f529687c16d89af Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Mon, 29 May 2023 19:46:46 +0200 Subject: [PATCH 150/178] ci: add stable build, add tests. --- .github/ci/build-stable.sh | 16 ++++++++++++++++ .github/ci/{rust.sh => build.sh} | 2 -- .github/ci/test.sh | 28 ++++++++++++++++++++++++++++ ci_stable.sh | 3 --- 4 files changed, 44 insertions(+), 5 deletions(-) create mode 100755 .github/ci/build-stable.sh rename .github/ci/{rust.sh => build.sh} (95%) create mode 100755 .github/ci/test.sh diff --git a/.github/ci/build-stable.sh b/.github/ci/build-stable.sh new file mode 100755 index 00000000..0dadd610 --- /dev/null +++ b/.github/ci/build-stable.sh @@ -0,0 +1,16 @@ +#!/bin/bash +## on push branch~=gh-readonly-queue/main/.* +## on pull_request + +set -euo pipefail + +export RUSTUP_HOME=/ci/cache/rustup +export CARGO_HOME=/ci/cache/cargo +export CARGO_TARGET_DIR=/ci/cache/target + +hashtime restore /ci/cache/filetime.json || true +hashtime save /ci/cache/filetime.json + +sed -i 's/channel.*/channel = "stable"/g' rust-toolchain.toml + +./ci_stable.sh diff --git a/.github/ci/rust.sh b/.github/ci/build.sh similarity index 95% rename from .github/ci/rust.sh rename to .github/ci/build.sh index db1fc853..1c3a7f3b 100755 --- a/.github/ci/rust.sh +++ b/.github/ci/build.sh @@ -4,8 +4,6 @@ set -euo pipefail -echo Hello World! - export RUSTUP_HOME=/ci/cache/rustup export CARGO_HOME=/ci/cache/cargo export CARGO_TARGET_DIR=/ci/cache/target diff --git a/.github/ci/test.sh b/.github/ci/test.sh new file mode 100755 index 00000000..a7140cfd --- /dev/null +++ b/.github/ci/test.sh @@ -0,0 +1,28 @@ +#!/bin/bash +## on push branch~=gh-readonly-queue/main/.* +## on pull_request + +set -euo pipefail + +export RUSTUP_HOME=/ci/cache/rustup +export CARGO_HOME=/ci/cache/cargo +export CARGO_TARGET_DIR=/ci/cache/target + +hashtime restore /ci/cache/filetime.json || true +hashtime save /ci/cache/filetime.json + +cargo test --manifest-path ./embassy-sync/Cargo.toml +cargo test --manifest-path ./embassy-embedded-hal/Cargo.toml +cargo test --manifest-path ./embassy-hal-common/Cargo.toml +cargo test --manifest-path ./embassy-time/Cargo.toml --features generic-queue + +cargo test --manifest-path ./embassy-boot/boot/Cargo.toml +cargo test --manifest-path ./embassy-boot/boot/Cargo.toml --features nightly +cargo test --manifest-path ./embassy-boot/boot/Cargo.toml --features nightly,ed25519-dalek +cargo test --manifest-path ./embassy-boot/boot/Cargo.toml --features nightly,ed25519-salty + +#cargo test --manifest-path ./embassy-nrf/Cargo.toml --no-default-features --features nightly,nrf52840,time-driver-rtc1 ## broken doctests + +cargo test --manifest-path ./embassy-stm32/Cargo.toml --no-default-features --features nightly,stm32f429vg,exti,time-driver-any,exti +cargo test --manifest-path ./embassy-stm32/Cargo.toml --no-default-features --features nightly,stm32f732ze,exti,time-driver-any,exti +cargo test --manifest-path ./embassy-stm32/Cargo.toml --no-default-features --features nightly,stm32f769ni,exti,time-driver-any,exti diff --git a/ci_stable.sh b/ci_stable.sh index 55a2f89a..a67087ff 100755 --- a/ci_stable.sh +++ b/ci_stable.sh @@ -2,12 +2,9 @@ set -euo pipefail -export CARGO_TARGET_DIR=$PWD/target_ci_stable export RUSTFLAGS=-Dwarnings export DEFMT_LOG=trace -sed -i 's/channel.*/channel = "stable"/g' rust-toolchain.toml - cargo batch \ --- build --release --manifest-path embassy-boot/nrf/Cargo.toml --target thumbv7em-none-eabi --features embassy-nrf/nrf52840 \ --- build --release --manifest-path embassy-boot/nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features embassy-nrf/nrf9160-ns \ From 1acbc5b1a90985df83195ad23f9665b3227b3805 Mon Sep 17 00:00:00 2001 From: Rasmus Melchior Jacobsen Date: Sat, 27 May 2023 19:26:45 +0200 Subject: [PATCH 151/178] Remove the usage of the local Partition type in BootLoader --- embassy-boot/boot/Cargo.toml | 1 + embassy-boot/boot/src/boot_loader.rs | 344 +++++++++------------------ 2 files changed, 114 insertions(+), 231 deletions(-) diff --git a/embassy-boot/boot/Cargo.toml b/embassy-boot/boot/Cargo.toml index f641d5e1..3fdf69c5 100644 --- a/embassy-boot/boot/Cargo.toml +++ b/embassy-boot/boot/Cargo.toml @@ -39,6 +39,7 @@ env_logger = "0.9" rand = "0.7" # ed25519-dalek v1.0.1 depends on this exact version futures = { version = "0.3", features = ["executor"] } sha1 = "0.10.5" +embassy-embedded-hal = { version = "0.1.0", path = "../../embassy-embedded-hal" } [dev-dependencies.ed25519-dalek] default_features = false diff --git a/embassy-boot/boot/src/boot_loader.rs b/embassy-boot/boot/src/boot_loader.rs index b959de2c..50eb5e66 100644 --- a/embassy-boot/boot/src/boot_loader.rs +++ b/embassy-boot/boot/src/boot_loader.rs @@ -1,6 +1,8 @@ use embedded_storage::nor_flash::{ErrorType, NorFlash, NorFlashError, NorFlashErrorKind, ReadNorFlash}; -use crate::{Partition, State, BOOT_MAGIC, SWAP_MAGIC}; +use crate::{State, BOOT_MAGIC, SWAP_MAGIC}; + +const STATE_ERASE_VALUE: u8 = 0xFF; /// Errors returned by bootloader #[derive(PartialEq, Eq, Debug)] @@ -30,65 +32,39 @@ where } } -/// Trait defining the flash handles used for active and DFU partition. -pub trait FlashConfig { - /// The erase value of the state flash. Typically the default of 0xFF is used, but some flashes use a different value. - const STATE_ERASE_VALUE: u8 = 0xFF; - /// Flash type used for the state partition. - type STATE: NorFlash; - /// Flash type used for the active partition. - type ACTIVE: NorFlash; - /// Flash type used for the dfu partition. - type DFU: NorFlash; - - /// Return flash instance used to write/read to/from active partition. - fn active(&mut self) -> &mut Self::ACTIVE; - /// Return flash instance used to write/read to/from dfu partition. - fn dfu(&mut self) -> &mut Self::DFU; - /// Return flash instance used to write/read to/from bootloader state. - fn state(&mut self) -> &mut Self::STATE; -} - -trait FlashConfigEx { - fn page_size() -> u32; -} - -impl FlashConfigEx for T { - /// Get the page size which is the "unit of operation" within the bootloader. - fn page_size() -> u32 { - core::cmp::max(T::ACTIVE::ERASE_SIZE, T::DFU::ERASE_SIZE) as u32 - } -} - /// BootLoader works with any flash implementing embedded_storage. -pub struct BootLoader { - // Page with current state of bootloader. The state partition has the following format: - // All ranges are in multiples of WRITE_SIZE bytes. - // | Range | Description | - // | 0..1 | Magic indicating bootloader state. BOOT_MAGIC means boot, SWAP_MAGIC means swap. | - // | 1..2 | Progress validity. ERASE_VALUE means valid, !ERASE_VALUE means invalid. | - // | 2..2 + N | Progress index used while swapping or reverting | - state: Partition, - // Location of the partition which will be booted from - active: Partition, - // Location of the partition which will be swapped in when requested - dfu: Partition, +pub struct BootLoader { + /// Flash type used for the active partition - the partition which will be booted from. + active: ACTIVE, + /// Flash type used for the dfu partition - he partition which will be swapped in when requested. + dfu: DFU, + /// Flash type used for the state partition. + /// + /// The state partition has the following format: + /// All ranges are in multiples of WRITE_SIZE bytes. + /// | Range | Description | + /// | 0..1 | Magic indicating bootloader state. BOOT_MAGIC means boot, SWAP_MAGIC means swap. | + /// | 1..2 | Progress validity. ERASE_VALUE means valid, !ERASE_VALUE means invalid. | + /// | 2..2 + N | Progress index used while swapping or reverting + state: STATE, } -impl BootLoader { - /// Create a new instance of a bootloader with the given partitions. +impl BootLoader { + /// Get the page size which is the "unit of operation" within the bootloader. + const PAGE_SIZE: u32 = if ACTIVE::ERASE_SIZE > DFU::ERASE_SIZE { + ACTIVE::ERASE_SIZE as u32 + } else { + DFU::ERASE_SIZE as u32 + }; + + /// Create a new instance of a bootloader with the flash partitions. /// /// - All partitions must be aligned with the PAGE_SIZE const generic parameter. /// - The dfu partition must be at least PAGE_SIZE bigger than the active partition. - pub fn new(active: Partition, dfu: Partition, state: Partition) -> Self { + pub fn new(active: ACTIVE, dfu: DFU, state: STATE) -> Self { Self { active, dfu, state } } - /// Return the offset of the active partition into the active flash. - pub fn boot_address(&self) -> usize { - self.active.from as usize - } - /// Perform necessary boot preparations like swapping images. /// /// The DFU partition is assumed to be 1 page bigger than the active partition for the swap @@ -175,195 +151,174 @@ impl BootLoader { /// | DFU | 3 | 3 | 2 | 1 | 3 | /// +-----------+--------------+--------+--------+--------+--------+ /// - pub fn prepare_boot(&mut self, p: &mut P, aligned_buf: &mut [u8]) -> Result { + pub fn prepare_boot(&mut self, aligned_buf: &mut [u8]) -> Result { // Ensure we have enough progress pages to store copy progress - assert_eq!(0, P::page_size() % aligned_buf.len() as u32); - assert_eq!(0, P::page_size() % P::ACTIVE::WRITE_SIZE as u32); - assert_eq!(0, P::page_size() % P::ACTIVE::ERASE_SIZE as u32); - assert_eq!(0, P::page_size() % P::DFU::WRITE_SIZE as u32); - assert_eq!(0, P::page_size() % P::DFU::ERASE_SIZE as u32); - assert!(aligned_buf.len() >= P::STATE::WRITE_SIZE); - assert_eq!(0, aligned_buf.len() % P::ACTIVE::WRITE_SIZE); - assert_eq!(0, aligned_buf.len() % P::DFU::WRITE_SIZE); - assert_partitions(self.active, self.dfu, self.state, P::page_size(), P::STATE::WRITE_SIZE); + assert_eq!(0, Self::PAGE_SIZE % aligned_buf.len() as u32); + assert_eq!(0, Self::PAGE_SIZE % ACTIVE::WRITE_SIZE as u32); + assert_eq!(0, Self::PAGE_SIZE % ACTIVE::ERASE_SIZE as u32); + assert_eq!(0, Self::PAGE_SIZE % DFU::WRITE_SIZE as u32); + assert_eq!(0, Self::PAGE_SIZE % DFU::ERASE_SIZE as u32); + assert!(aligned_buf.len() >= STATE::WRITE_SIZE); + assert_eq!(0, aligned_buf.len() % ACTIVE::WRITE_SIZE); + assert_eq!(0, aligned_buf.len() % DFU::WRITE_SIZE); + + assert_partitions(&self.active, &self.dfu, &self.state, Self::PAGE_SIZE); // Copy contents from partition N to active - let state = self.read_state(p, aligned_buf)?; + let state = self.read_state(aligned_buf)?; if state == State::Swap { // // Check if we already swapped. If we're in the swap state, this means we should revert // since the app has failed to mark boot as successful // - if !self.is_swapped(p, aligned_buf)? { + if !self.is_swapped(aligned_buf)? { trace!("Swapping"); - self.swap(p, aligned_buf)?; + self.swap(aligned_buf)?; trace!("Swapping done"); } else { trace!("Reverting"); - self.revert(p, aligned_buf)?; + self.revert(aligned_buf)?; - let state_flash = p.state(); - let state_word = &mut aligned_buf[..P::STATE::WRITE_SIZE]; + let state_word = &mut aligned_buf[..STATE::WRITE_SIZE]; // Invalidate progress - state_word.fill(!P::STATE_ERASE_VALUE); - self.state - .write_blocking(state_flash, P::STATE::WRITE_SIZE as u32, state_word)?; + state_word.fill(!STATE_ERASE_VALUE); + self.state.write(STATE::WRITE_SIZE as u32, state_word)?; // Clear magic and progress - self.state.wipe_blocking(state_flash)?; + self.state.erase(0, self.state.capacity() as u32)?; // Set magic state_word.fill(BOOT_MAGIC); - self.state.write_blocking(state_flash, 0, state_word)?; + self.state.write(0, state_word)?; } } Ok(state) } - fn is_swapped(&mut self, p: &mut P, aligned_buf: &mut [u8]) -> Result { - let page_count = (self.active.size() / P::page_size()) as usize; - let progress = self.current_progress(p, aligned_buf)?; + fn is_swapped(&mut self, aligned_buf: &mut [u8]) -> Result { + let page_count = self.active.capacity() / Self::PAGE_SIZE as usize; + let progress = self.current_progress(aligned_buf)?; Ok(progress >= page_count * 2) } - fn current_progress(&mut self, config: &mut P, aligned_buf: &mut [u8]) -> Result { - let write_size = P::STATE::WRITE_SIZE as u32; - let max_index = (((self.state.size() - write_size) / write_size) - 2) as usize; - let state_flash = config.state(); + fn current_progress(&mut self, aligned_buf: &mut [u8]) -> Result { + let write_size = STATE::WRITE_SIZE as u32; + let max_index = ((self.state.capacity() - STATE::WRITE_SIZE) / STATE::WRITE_SIZE) - 2; let state_word = &mut aligned_buf[..write_size as usize]; - self.state.read_blocking(state_flash, write_size, state_word)?; - if state_word.iter().any(|&b| b != P::STATE_ERASE_VALUE) { + self.state.read(write_size, state_word)?; + if state_word.iter().any(|&b| b != STATE_ERASE_VALUE) { // Progress is invalid return Ok(max_index); } for index in 0..max_index { - self.state - .read_blocking(state_flash, (2 + index) as u32 * write_size, state_word)?; + self.state.read((2 + index) as u32 * write_size, state_word)?; - if state_word.iter().any(|&b| b == P::STATE_ERASE_VALUE) { + if state_word.iter().any(|&b| b == STATE_ERASE_VALUE) { return Ok(index); } } Ok(max_index) } - fn update_progress( - &mut self, - progress_index: usize, - p: &mut P, - aligned_buf: &mut [u8], - ) -> Result<(), BootError> { - let state_word = &mut aligned_buf[..P::STATE::WRITE_SIZE]; - state_word.fill(!P::STATE_ERASE_VALUE); - self.state.write_blocking( - p.state(), - (2 + progress_index) as u32 * P::STATE::WRITE_SIZE as u32, - state_word, - )?; + fn update_progress(&mut self, progress_index: usize, aligned_buf: &mut [u8]) -> Result<(), BootError> { + let state_word = &mut aligned_buf[..STATE::WRITE_SIZE]; + state_word.fill(!STATE_ERASE_VALUE); + self.state + .write((2 + progress_index) as u32 * STATE::WRITE_SIZE as u32, state_word)?; Ok(()) } - fn copy_page_once_to_active( + fn copy_page_once_to_active( &mut self, progress_index: usize, from_offset: u32, to_offset: u32, - p: &mut P, aligned_buf: &mut [u8], ) -> Result<(), BootError> { - if self.current_progress(p, aligned_buf)? <= progress_index { - let page_size = P::page_size() as u32; + if self.current_progress(aligned_buf)? <= progress_index { + let page_size = Self::PAGE_SIZE as u32; - self.active - .erase_blocking(p.active(), to_offset, to_offset + page_size)?; + self.active.erase(to_offset, to_offset + page_size)?; for offset_in_page in (0..page_size).step_by(aligned_buf.len()) { - self.dfu - .read_blocking(p.dfu(), from_offset + offset_in_page as u32, aligned_buf)?; - self.active - .write_blocking(p.active(), to_offset + offset_in_page as u32, aligned_buf)?; + self.dfu.read(from_offset + offset_in_page as u32, aligned_buf)?; + self.active.write(to_offset + offset_in_page as u32, aligned_buf)?; } - self.update_progress(progress_index, p, aligned_buf)?; + self.update_progress(progress_index, aligned_buf)?; } Ok(()) } - fn copy_page_once_to_dfu( + fn copy_page_once_to_dfu( &mut self, progress_index: usize, from_offset: u32, to_offset: u32, - p: &mut P, aligned_buf: &mut [u8], ) -> Result<(), BootError> { - if self.current_progress(p, aligned_buf)? <= progress_index { - let page_size = P::page_size() as u32; + if self.current_progress(aligned_buf)? <= progress_index { + let page_size = Self::PAGE_SIZE as u32; - self.dfu - .erase_blocking(p.dfu(), to_offset as u32, to_offset + page_size)?; + self.dfu.erase(to_offset as u32, to_offset + page_size)?; for offset_in_page in (0..page_size).step_by(aligned_buf.len()) { - self.active - .read_blocking(p.active(), from_offset + offset_in_page as u32, aligned_buf)?; - self.dfu - .write_blocking(p.dfu(), to_offset + offset_in_page as u32, aligned_buf)?; + self.active.read(from_offset + offset_in_page as u32, aligned_buf)?; + self.dfu.write(to_offset + offset_in_page as u32, aligned_buf)?; } - self.update_progress(progress_index, p, aligned_buf)?; + self.update_progress(progress_index, aligned_buf)?; } Ok(()) } - fn swap(&mut self, p: &mut P, aligned_buf: &mut [u8]) -> Result<(), BootError> { - let page_size = P::page_size(); - let page_count = self.active.size() / page_size; + fn swap(&mut self, aligned_buf: &mut [u8]) -> Result<(), BootError> { + let page_count = self.active.capacity() as u32 / Self::PAGE_SIZE; for page_num in 0..page_count { let progress_index = (page_num * 2) as usize; // Copy active page to the 'next' DFU page. - let active_from_offset = (page_count - 1 - page_num) * page_size; - let dfu_to_offset = (page_count - page_num) * page_size; + let active_from_offset = (page_count - 1 - page_num) * Self::PAGE_SIZE; + let dfu_to_offset = (page_count - page_num) * Self::PAGE_SIZE; //trace!("Copy active {} to dfu {}", active_from_offset, dfu_to_offset); - self.copy_page_once_to_dfu(progress_index, active_from_offset, dfu_to_offset, p, aligned_buf)?; + self.copy_page_once_to_dfu(progress_index, active_from_offset, dfu_to_offset, aligned_buf)?; // Copy DFU page to the active page - let active_to_offset = (page_count - 1 - page_num) * page_size; - let dfu_from_offset = (page_count - 1 - page_num) * page_size; + let active_to_offset = (page_count - 1 - page_num) * Self::PAGE_SIZE; + let dfu_from_offset = (page_count - 1 - page_num) * Self::PAGE_SIZE; //trace!("Copy dfy {} to active {}", dfu_from_offset, active_to_offset); - self.copy_page_once_to_active(progress_index + 1, dfu_from_offset, active_to_offset, p, aligned_buf)?; + self.copy_page_once_to_active(progress_index + 1, dfu_from_offset, active_to_offset, aligned_buf)?; } Ok(()) } - fn revert(&mut self, p: &mut P, aligned_buf: &mut [u8]) -> Result<(), BootError> { - let page_size = P::page_size(); - let page_count = self.active.size() / page_size; + fn revert(&mut self, aligned_buf: &mut [u8]) -> Result<(), BootError> { + let page_count = self.active.capacity() as u32 / Self::PAGE_SIZE; for page_num in 0..page_count { let progress_index = (page_count * 2 + page_num * 2) as usize; // Copy the bad active page to the DFU page - let active_from_offset = page_num * page_size; - let dfu_to_offset = page_num * page_size; - self.copy_page_once_to_dfu(progress_index, active_from_offset, dfu_to_offset, p, aligned_buf)?; + let active_from_offset = page_num * Self::PAGE_SIZE; + let dfu_to_offset = page_num * Self::PAGE_SIZE; + self.copy_page_once_to_dfu(progress_index, active_from_offset, dfu_to_offset, aligned_buf)?; // Copy the DFU page back to the active page - let active_to_offset = page_num * page_size; - let dfu_from_offset = (page_num + 1) * page_size; - self.copy_page_once_to_active(progress_index + 1, dfu_from_offset, active_to_offset, p, aligned_buf)?; + let active_to_offset = page_num * Self::PAGE_SIZE; + let dfu_from_offset = (page_num + 1) * Self::PAGE_SIZE; + self.copy_page_once_to_active(progress_index + 1, dfu_from_offset, active_to_offset, aligned_buf)?; } Ok(()) } - fn read_state(&mut self, config: &mut P, aligned_buf: &mut [u8]) -> Result { - let state_word = &mut aligned_buf[..P::STATE::WRITE_SIZE]; - self.state.read_blocking(config.state(), 0, state_word)?; + fn read_state(&mut self, aligned_buf: &mut [u8]) -> Result { + let state_word = &mut aligned_buf[..STATE::WRITE_SIZE]; + self.state.read(0, state_word)?; if !state_word.iter().any(|&b| b != SWAP_MAGIC) { Ok(State::Swap) @@ -373,11 +328,16 @@ impl BootLoader { } } -fn assert_partitions(active: Partition, dfu: Partition, state: Partition, page_size: u32, state_write_size: usize) { - assert_eq!(active.size() % page_size, 0); - assert_eq!(dfu.size() % page_size, 0); - assert!(dfu.size() - active.size() >= page_size); - assert!(2 + 2 * (active.size() / page_size) <= state.size() / state_write_size as u32); +fn assert_partitions( + active: &ACTIVE, + dfu: &DFU, + state: &STATE, + page_size: u32, +) { + assert_eq!(active.capacity() as u32 % page_size, 0); + assert_eq!(dfu.capacity() as u32 % page_size, 0); + assert!(dfu.capacity() as u32 - active.capacity() as u32 >= page_size); + assert!(2 + 2 * (active.capacity() as u32 / page_size) <= state.capacity() as u32 / STATE::WRITE_SIZE as u32); } /// A flash wrapper implementing the Flash and embedded_storage traits. @@ -436,98 +396,20 @@ where } } -/// Convenience provider that uses a single flash for all partitions. -pub struct SingleFlashConfig<'a, F> -where - F: NorFlash, -{ - flash: &'a mut F, -} - -impl<'a, F> SingleFlashConfig<'a, F> -where - F: NorFlash, -{ - /// Create a provider for a single flash. - pub fn new(flash: &'a mut F) -> Self { - Self { flash } - } -} - -impl<'a, F> FlashConfig for SingleFlashConfig<'a, F> -where - F: NorFlash, -{ - type STATE = F; - type ACTIVE = F; - type DFU = F; - - fn active(&mut self) -> &mut Self::STATE { - self.flash - } - fn dfu(&mut self) -> &mut Self::ACTIVE { - self.flash - } - fn state(&mut self) -> &mut Self::DFU { - self.flash - } -} - -/// Convenience flash provider that uses separate flash instances for each partition. -pub struct MultiFlashConfig<'a, ACTIVE, STATE, DFU> -where - ACTIVE: NorFlash, - STATE: NorFlash, - DFU: NorFlash, -{ - active: &'a mut ACTIVE, - state: &'a mut STATE, - dfu: &'a mut DFU, -} - -impl<'a, ACTIVE, STATE, DFU> MultiFlashConfig<'a, ACTIVE, STATE, DFU> -where - ACTIVE: NorFlash, - STATE: NorFlash, - DFU: NorFlash, -{ - /// Create a new flash provider with separate configuration for all three partitions. - pub fn new(active: &'a mut ACTIVE, state: &'a mut STATE, dfu: &'a mut DFU) -> Self { - Self { active, state, dfu } - } -} - -impl<'a, ACTIVE, STATE, DFU> FlashConfig for MultiFlashConfig<'a, ACTIVE, STATE, DFU> -where - ACTIVE: NorFlash, - STATE: NorFlash, - DFU: NorFlash, -{ - type STATE = STATE; - type ACTIVE = ACTIVE; - type DFU = DFU; - - fn active(&mut self) -> &mut Self::ACTIVE { - self.active - } - fn dfu(&mut self) -> &mut Self::DFU { - self.dfu - } - fn state(&mut self) -> &mut Self::STATE { - self.state - } -} - #[cfg(test)] mod tests { use super::*; + use crate::mem_flash::MemFlash; #[test] #[should_panic] fn test_range_asserts() { - const ACTIVE: Partition = Partition::new(4096, 4194304); - const DFU: Partition = Partition::new(4194304, 2 * 4194304); - const STATE: Partition = Partition::new(0, 4096); - assert_partitions(ACTIVE, DFU, STATE, 4096, 4); + const ACTIVE_SIZE: usize = 4194304 - 4096; + const DFU_SIZE: usize = 4194304; + const STATE_SIZE: usize = 4096; + static ACTIVE: MemFlash = MemFlash::new(0xFF); + static DFU: MemFlash = MemFlash::new(0xFF); + static STATE: MemFlash = MemFlash::new(0xFF); + assert_partitions(&ACTIVE, &DFU, &STATE, 4096); } } From c844894a6e25bbf38c10ed7e60ee554e565b56b1 Mon Sep 17 00:00:00 2001 From: Rasmus Melchior Jacobsen Date: Mon, 29 May 2023 21:29:13 +0200 Subject: [PATCH 152/178] Split the FirmwareUpdater into blocking and async --- embassy-boot/boot/src/firmware_updater.rs | 543 ------------------ .../boot/src/firmware_updater/asynch.rs | 251 ++++++++ .../boot/src/firmware_updater/blocking.rs | 221 +++++++ embassy-boot/boot/src/firmware_updater/mod.rs | 71 +++ 4 files changed, 543 insertions(+), 543 deletions(-) delete mode 100644 embassy-boot/boot/src/firmware_updater.rs create mode 100644 embassy-boot/boot/src/firmware_updater/asynch.rs create mode 100644 embassy-boot/boot/src/firmware_updater/blocking.rs create mode 100644 embassy-boot/boot/src/firmware_updater/mod.rs diff --git a/embassy-boot/boot/src/firmware_updater.rs b/embassy-boot/boot/src/firmware_updater.rs deleted file mode 100644 index aeea206f..00000000 --- a/embassy-boot/boot/src/firmware_updater.rs +++ /dev/null @@ -1,543 +0,0 @@ -use digest::Digest; -use embedded_storage::nor_flash::{NorFlash, NorFlashError, NorFlashErrorKind}; -#[cfg(feature = "nightly")] -use embedded_storage_async::nor_flash::NorFlash as AsyncNorFlash; - -use crate::{Partition, State, BOOT_MAGIC, SWAP_MAGIC}; - -/// Errors returned by FirmwareUpdater -#[derive(Debug)] -pub enum FirmwareUpdaterError { - /// Error from flash. - Flash(NorFlashErrorKind), - /// Signature errors. - Signature(signature::Error), -} - -#[cfg(feature = "defmt")] -impl defmt::Format for FirmwareUpdaterError { - fn format(&self, fmt: defmt::Formatter) { - match self { - FirmwareUpdaterError::Flash(_) => defmt::write!(fmt, "FirmwareUpdaterError::Flash(_)"), - FirmwareUpdaterError::Signature(_) => defmt::write!(fmt, "FirmwareUpdaterError::Signature(_)"), - } - } -} - -impl From for FirmwareUpdaterError -where - E: NorFlashError, -{ - fn from(error: E) -> Self { - FirmwareUpdaterError::Flash(error.kind()) - } -} - -/// FirmwareUpdater is an application API for interacting with the BootLoader without the ability to -/// 'mess up' the internal bootloader state -pub struct FirmwareUpdater { - state: Partition, - dfu: Partition, -} - -#[cfg(target_os = "none")] -impl Default for FirmwareUpdater { - fn default() -> Self { - extern "C" { - static __bootloader_state_start: u32; - static __bootloader_state_end: u32; - static __bootloader_dfu_start: u32; - static __bootloader_dfu_end: u32; - } - - let dfu = unsafe { - Partition::new( - &__bootloader_dfu_start as *const u32 as u32, - &__bootloader_dfu_end as *const u32 as u32, - ) - }; - let state = unsafe { - Partition::new( - &__bootloader_state_start as *const u32 as u32, - &__bootloader_state_end as *const u32 as u32, - ) - }; - - trace!("DFU: 0x{:x} - 0x{:x}", dfu.from, dfu.to); - trace!("STATE: 0x{:x} - 0x{:x}", state.from, state.to); - FirmwareUpdater::new(dfu, state) - } -} - -impl FirmwareUpdater { - /// Create a firmware updater instance with partition ranges for the update and state partitions. - pub const fn new(dfu: Partition, state: Partition) -> Self { - Self { dfu, state } - } - - /// Obtain the current state. - /// - /// This is useful to check if the bootloader has just done a swap, in order - /// to do verifications and self-tests of the new image before calling - /// `mark_booted`. - #[cfg(feature = "nightly")] - pub async fn get_state( - &mut self, - state_flash: &mut F, - aligned: &mut [u8], - ) -> Result { - self.state.read(state_flash, 0, aligned).await?; - - if !aligned.iter().any(|&b| b != SWAP_MAGIC) { - Ok(State::Swap) - } else { - Ok(State::Boot) - } - } - - /// Verify the DFU given a public key. If there is an error then DO NOT - /// proceed with updating the firmware as it must be signed with a - /// corresponding private key (otherwise it could be malicious firmware). - /// - /// Mark to trigger firmware swap on next boot if verify suceeds. - /// - /// If the "ed25519-salty" feature is set (or another similar feature) then the signature is expected to have - /// been generated from a SHA-512 digest of the firmware bytes. - /// - /// If no signature feature is set then this method will always return a - /// signature error. - /// - /// # Safety - /// - /// The `_aligned` buffer must have a size of F::WRITE_SIZE, and follow the alignment rules for the flash being read from - /// and written to. - #[cfg(all(feature = "_verify", feature = "nightly"))] - pub async fn verify_and_mark_updated( - &mut self, - _state_and_dfu_flash: &mut F, - _public_key: &[u8], - _signature: &[u8], - _update_len: u32, - _aligned: &mut [u8], - ) -> Result<(), FirmwareUpdaterError> { - assert_eq!(_aligned.len(), F::WRITE_SIZE); - assert!(_update_len <= self.dfu.size()); - - #[cfg(feature = "ed25519-dalek")] - { - use ed25519_dalek::{PublicKey, Signature, SignatureError, Verifier}; - - use crate::digest_adapters::ed25519_dalek::Sha512; - - let into_signature_error = |e: SignatureError| FirmwareUpdaterError::Signature(e.into()); - - let public_key = PublicKey::from_bytes(_public_key).map_err(into_signature_error)?; - let signature = Signature::from_bytes(_signature).map_err(into_signature_error)?; - - let mut message = [0; 64]; - self.hash::<_, Sha512>(_state_and_dfu_flash, _update_len, _aligned, &mut message) - .await?; - - public_key.verify(&message, &signature).map_err(into_signature_error)? - } - #[cfg(feature = "ed25519-salty")] - { - use salty::constants::{PUBLICKEY_SERIALIZED_LENGTH, SIGNATURE_SERIALIZED_LENGTH}; - use salty::{PublicKey, Signature}; - - use crate::digest_adapters::salty::Sha512; - - fn into_signature_error(_: E) -> FirmwareUpdaterError { - FirmwareUpdaterError::Signature(signature::Error::default()) - } - - let public_key: [u8; PUBLICKEY_SERIALIZED_LENGTH] = _public_key.try_into().map_err(into_signature_error)?; - let public_key = PublicKey::try_from(&public_key).map_err(into_signature_error)?; - let signature: [u8; SIGNATURE_SERIALIZED_LENGTH] = _signature.try_into().map_err(into_signature_error)?; - let signature = Signature::try_from(&signature).map_err(into_signature_error)?; - - let mut message = [0; 64]; - self.hash::<_, Sha512>(_state_and_dfu_flash, _update_len, _aligned, &mut message) - .await?; - - let r = public_key.verify(&message, &signature); - trace!( - "Verifying with public key {}, signature {} and message {} yields ok: {}", - public_key.to_bytes(), - signature.to_bytes(), - message, - r.is_ok() - ); - r.map_err(into_signature_error)? - } - - self.set_magic(_aligned, SWAP_MAGIC, _state_and_dfu_flash).await - } - - /// Verify the update in DFU with any digest. - #[cfg(feature = "nightly")] - pub async fn hash( - &mut self, - dfu_flash: &mut F, - update_len: u32, - chunk_buf: &mut [u8], - output: &mut [u8], - ) -> Result<(), FirmwareUpdaterError> { - let mut digest = D::new(); - for offset in (0..update_len).step_by(chunk_buf.len()) { - self.dfu.read(dfu_flash, offset, chunk_buf).await?; - let len = core::cmp::min((update_len - offset) as usize, chunk_buf.len()); - digest.update(&chunk_buf[..len]); - } - output.copy_from_slice(digest.finalize().as_slice()); - Ok(()) - } - - /// Mark to trigger firmware swap on next boot. - /// - /// # Safety - /// - /// The `aligned` buffer must have a size of F::WRITE_SIZE, and follow the alignment rules for the flash being written to. - #[cfg(all(feature = "nightly", not(feature = "_verify")))] - pub async fn mark_updated( - &mut self, - state_flash: &mut F, - aligned: &mut [u8], - ) -> Result<(), FirmwareUpdaterError> { - assert_eq!(aligned.len(), F::WRITE_SIZE); - self.set_magic(aligned, SWAP_MAGIC, state_flash).await - } - - /// Mark firmware boot successful and stop rollback on reset. - /// - /// # Safety - /// - /// The `aligned` buffer must have a size of F::WRITE_SIZE, and follow the alignment rules for the flash being written to. - #[cfg(feature = "nightly")] - pub async fn mark_booted( - &mut self, - state_flash: &mut F, - aligned: &mut [u8], - ) -> Result<(), FirmwareUpdaterError> { - assert_eq!(aligned.len(), F::WRITE_SIZE); - self.set_magic(aligned, BOOT_MAGIC, state_flash).await - } - - #[cfg(feature = "nightly")] - async fn set_magic( - &mut self, - aligned: &mut [u8], - magic: u8, - state_flash: &mut F, - ) -> Result<(), FirmwareUpdaterError> { - self.state.read(state_flash, 0, aligned).await?; - - if aligned.iter().any(|&b| b != magic) { - // Read progress validity - self.state.read(state_flash, F::WRITE_SIZE as u32, aligned).await?; - - // FIXME: Do not make this assumption. - const STATE_ERASE_VALUE: u8 = 0xFF; - - if aligned.iter().any(|&b| b != STATE_ERASE_VALUE) { - // The current progress validity marker is invalid - } else { - // Invalidate progress - aligned.fill(!STATE_ERASE_VALUE); - self.state.write(state_flash, F::WRITE_SIZE as u32, aligned).await?; - } - - // Clear magic and progress - self.state.wipe(state_flash).await?; - - // Set magic - aligned.fill(magic); - self.state.write(state_flash, 0, aligned).await?; - } - Ok(()) - } - - /// Write data to a flash page. - /// - /// The buffer must follow alignment requirements of the target flash and a multiple of page size big. - /// - /// # Safety - /// - /// Failing to meet alignment and size requirements may result in a panic. - #[cfg(feature = "nightly")] - pub async fn write_firmware( - &mut self, - offset: usize, - data: &[u8], - dfu_flash: &mut F, - ) -> Result<(), FirmwareUpdaterError> { - assert!(data.len() >= F::ERASE_SIZE); - - self.dfu - .erase(dfu_flash, offset as u32, (offset + data.len()) as u32) - .await?; - - self.dfu.write(dfu_flash, offset as u32, data).await?; - - Ok(()) - } - - /// Prepare for an incoming DFU update by erasing the entire DFU area and - /// returning its `Partition`. - /// - /// Using this instead of `write_firmware` allows for an optimized API in - /// exchange for added complexity. - #[cfg(feature = "nightly")] - pub async fn prepare_update( - &mut self, - dfu_flash: &mut F, - ) -> Result { - self.dfu.wipe(dfu_flash).await?; - - Ok(self.dfu) - } - - // - // Blocking API - // - - /// Obtain the current state. - /// - /// This is useful to check if the bootloader has just done a swap, in order - /// to do verifications and self-tests of the new image before calling - /// `mark_booted`. - pub fn get_state_blocking( - &mut self, - state_flash: &mut F, - aligned: &mut [u8], - ) -> Result { - self.state.read_blocking(state_flash, 0, aligned)?; - - if !aligned.iter().any(|&b| b != SWAP_MAGIC) { - Ok(State::Swap) - } else { - Ok(State::Boot) - } - } - - /// Verify the DFU given a public key. If there is an error then DO NOT - /// proceed with updating the firmware as it must be signed with a - /// corresponding private key (otherwise it could be malicious firmware). - /// - /// Mark to trigger firmware swap on next boot if verify suceeds. - /// - /// If the "ed25519-salty" feature is set (or another similar feature) then the signature is expected to have - /// been generated from a SHA-512 digest of the firmware bytes. - /// - /// If no signature feature is set then this method will always return a - /// signature error. - /// - /// # Safety - /// - /// The `_aligned` buffer must have a size of F::WRITE_SIZE, and follow the alignment rules for the flash being read from - /// and written to. - #[cfg(feature = "_verify")] - pub fn verify_and_mark_updated_blocking( - &mut self, - _state_and_dfu_flash: &mut F, - _public_key: &[u8], - _signature: &[u8], - _update_len: u32, - _aligned: &mut [u8], - ) -> Result<(), FirmwareUpdaterError> { - assert_eq!(_aligned.len(), F::WRITE_SIZE); - assert!(_update_len <= self.dfu.size()); - - #[cfg(feature = "ed25519-dalek")] - { - use ed25519_dalek::{PublicKey, Signature, SignatureError, Verifier}; - - use crate::digest_adapters::ed25519_dalek::Sha512; - - let into_signature_error = |e: SignatureError| FirmwareUpdaterError::Signature(e.into()); - - let public_key = PublicKey::from_bytes(_public_key).map_err(into_signature_error)?; - let signature = Signature::from_bytes(_signature).map_err(into_signature_error)?; - - let mut message = [0; 64]; - self.hash_blocking::<_, Sha512>(_state_and_dfu_flash, _update_len, _aligned, &mut message)?; - - public_key.verify(&message, &signature).map_err(into_signature_error)? - } - #[cfg(feature = "ed25519-salty")] - { - use salty::constants::{PUBLICKEY_SERIALIZED_LENGTH, SIGNATURE_SERIALIZED_LENGTH}; - use salty::{PublicKey, Signature}; - - use crate::digest_adapters::salty::Sha512; - - fn into_signature_error(_: E) -> FirmwareUpdaterError { - FirmwareUpdaterError::Signature(signature::Error::default()) - } - - let public_key: [u8; PUBLICKEY_SERIALIZED_LENGTH] = _public_key.try_into().map_err(into_signature_error)?; - let public_key = PublicKey::try_from(&public_key).map_err(into_signature_error)?; - let signature: [u8; SIGNATURE_SERIALIZED_LENGTH] = _signature.try_into().map_err(into_signature_error)?; - let signature = Signature::try_from(&signature).map_err(into_signature_error)?; - - let mut message = [0; 64]; - self.hash_blocking::<_, Sha512>(_state_and_dfu_flash, _update_len, _aligned, &mut message)?; - - let r = public_key.verify(&message, &signature); - trace!( - "Verifying with public key {}, signature {} and message {} yields ok: {}", - public_key.to_bytes(), - signature.to_bytes(), - message, - r.is_ok() - ); - r.map_err(into_signature_error)? - } - - self.set_magic_blocking(_aligned, SWAP_MAGIC, _state_and_dfu_flash) - } - - /// Verify the update in DFU with any digest. - pub fn hash_blocking( - &mut self, - dfu_flash: &mut F, - update_len: u32, - chunk_buf: &mut [u8], - output: &mut [u8], - ) -> Result<(), FirmwareUpdaterError> { - let mut digest = D::new(); - for offset in (0..update_len).step_by(chunk_buf.len()) { - self.dfu.read_blocking(dfu_flash, offset, chunk_buf)?; - let len = core::cmp::min((update_len - offset) as usize, chunk_buf.len()); - digest.update(&chunk_buf[..len]); - } - output.copy_from_slice(digest.finalize().as_slice()); - Ok(()) - } - - /// Mark to trigger firmware swap on next boot. - /// - /// # Safety - /// - /// The `aligned` buffer must have a size of F::WRITE_SIZE, and follow the alignment rules for the flash being written to. - #[cfg(not(feature = "_verify"))] - pub fn mark_updated_blocking( - &mut self, - state_flash: &mut F, - aligned: &mut [u8], - ) -> Result<(), FirmwareUpdaterError> { - assert_eq!(aligned.len(), F::WRITE_SIZE); - self.set_magic_blocking(aligned, SWAP_MAGIC, state_flash) - } - - /// Mark firmware boot successful and stop rollback on reset. - /// - /// # Safety - /// - /// The `aligned` buffer must have a size of F::WRITE_SIZE, and follow the alignment rules for the flash being written to. - pub fn mark_booted_blocking( - &mut self, - state_flash: &mut F, - aligned: &mut [u8], - ) -> Result<(), FirmwareUpdaterError> { - assert_eq!(aligned.len(), F::WRITE_SIZE); - self.set_magic_blocking(aligned, BOOT_MAGIC, state_flash) - } - - fn set_magic_blocking( - &mut self, - aligned: &mut [u8], - magic: u8, - state_flash: &mut F, - ) -> Result<(), FirmwareUpdaterError> { - self.state.read_blocking(state_flash, 0, aligned)?; - - if aligned.iter().any(|&b| b != magic) { - // Read progress validity - self.state.read_blocking(state_flash, F::WRITE_SIZE as u32, aligned)?; - - // FIXME: Do not make this assumption. - const STATE_ERASE_VALUE: u8 = 0xFF; - - if aligned.iter().any(|&b| b != STATE_ERASE_VALUE) { - // The current progress validity marker is invalid - } else { - // Invalidate progress - aligned.fill(!STATE_ERASE_VALUE); - self.state.write_blocking(state_flash, F::WRITE_SIZE as u32, aligned)?; - } - - // Clear magic and progress - self.state.wipe_blocking(state_flash)?; - - // Set magic - aligned.fill(magic); - self.state.write_blocking(state_flash, 0, aligned)?; - } - Ok(()) - } - - /// Write data to a flash page. - /// - /// The buffer must follow alignment requirements of the target flash and a multiple of page size big. - /// - /// # Safety - /// - /// Failing to meet alignment and size requirements may result in a panic. - pub fn write_firmware_blocking( - &mut self, - offset: usize, - data: &[u8], - dfu_flash: &mut F, - ) -> Result<(), FirmwareUpdaterError> { - assert!(data.len() >= F::ERASE_SIZE); - - self.dfu - .erase_blocking(dfu_flash, offset as u32, (offset + data.len()) as u32)?; - - self.dfu.write_blocking(dfu_flash, offset as u32, data)?; - - Ok(()) - } - - /// Prepare for an incoming DFU update by erasing the entire DFU area and - /// returning its `Partition`. - /// - /// Using this instead of `write_firmware_blocking` allows for an optimized - /// API in exchange for added complexity. - pub fn prepare_update_blocking(&mut self, flash: &mut F) -> Result { - self.dfu.wipe_blocking(flash)?; - - Ok(self.dfu) - } -} - -#[cfg(test)] -mod tests { - use futures::executor::block_on; - use sha1::{Digest, Sha1}; - - use super::*; - use crate::mem_flash::MemFlash; - - #[test] - #[cfg(feature = "nightly")] - fn can_verify_sha1() { - const STATE: Partition = Partition::new(0, 4096); - const DFU: Partition = Partition::new(65536, 131072); - - let mut flash = MemFlash::<131072, 4096, 8>::default(); - - let update = [0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66]; - let mut to_write = [0; 4096]; - to_write[..7].copy_from_slice(update.as_slice()); - - let mut updater = FirmwareUpdater::new(DFU, STATE); - block_on(updater.write_firmware(0, to_write.as_slice(), &mut flash)).unwrap(); - let mut chunk_buf = [0; 2]; - let mut hash = [0; 20]; - block_on(updater.hash::<_, Sha1>(&mut flash, update.len() as u32, &mut chunk_buf, &mut hash)).unwrap(); - - assert_eq!(Sha1::digest(update).as_slice(), hash); - } -} diff --git a/embassy-boot/boot/src/firmware_updater/asynch.rs b/embassy-boot/boot/src/firmware_updater/asynch.rs new file mode 100644 index 00000000..bdd03bff --- /dev/null +++ b/embassy-boot/boot/src/firmware_updater/asynch.rs @@ -0,0 +1,251 @@ +use digest::Digest; +use embedded_storage_async::nor_flash::NorFlash as AsyncNorFlash; + +use crate::{FirmwareUpdater, FirmwareUpdaterError, Partition, State, BOOT_MAGIC, SWAP_MAGIC}; + +impl FirmwareUpdater { + /// Obtain the current state. + /// + /// This is useful to check if the bootloader has just done a swap, in order + /// to do verifications and self-tests of the new image before calling + /// `mark_booted`. + pub async fn get_state( + &mut self, + state_flash: &mut F, + aligned: &mut [u8], + ) -> Result { + self.state.read(state_flash, 0, aligned).await?; + + if !aligned.iter().any(|&b| b != SWAP_MAGIC) { + Ok(State::Swap) + } else { + Ok(State::Boot) + } + } + + /// Verify the DFU given a public key. If there is an error then DO NOT + /// proceed with updating the firmware as it must be signed with a + /// corresponding private key (otherwise it could be malicious firmware). + /// + /// Mark to trigger firmware swap on next boot if verify suceeds. + /// + /// If the "ed25519-salty" feature is set (or another similar feature) then the signature is expected to have + /// been generated from a SHA-512 digest of the firmware bytes. + /// + /// If no signature feature is set then this method will always return a + /// signature error. + /// + /// # Safety + /// + /// The `_aligned` buffer must have a size of F::WRITE_SIZE, and follow the alignment rules for the flash being read from + /// and written to. + #[cfg(all(feature = "_verify", feature = "nightly"))] + pub async fn verify_and_mark_updated( + &mut self, + _state_and_dfu_flash: &mut F, + _public_key: &[u8], + _signature: &[u8], + _update_len: u32, + _aligned: &mut [u8], + ) -> Result<(), FirmwareUpdaterError> { + assert_eq!(_aligned.len(), F::WRITE_SIZE); + assert!(_update_len <= self.dfu.size()); + + #[cfg(feature = "ed25519-dalek")] + { + use ed25519_dalek::{PublicKey, Signature, SignatureError, Verifier}; + + use crate::digest_adapters::ed25519_dalek::Sha512; + + let into_signature_error = |e: SignatureError| FirmwareUpdaterError::Signature(e.into()); + + let public_key = PublicKey::from_bytes(_public_key).map_err(into_signature_error)?; + let signature = Signature::from_bytes(_signature).map_err(into_signature_error)?; + + let mut message = [0; 64]; + self.hash::<_, Sha512>(_state_and_dfu_flash, _update_len, _aligned, &mut message) + .await?; + + public_key.verify(&message, &signature).map_err(into_signature_error)? + } + #[cfg(feature = "ed25519-salty")] + { + use salty::constants::{PUBLICKEY_SERIALIZED_LENGTH, SIGNATURE_SERIALIZED_LENGTH}; + use salty::{PublicKey, Signature}; + + use crate::digest_adapters::salty::Sha512; + + fn into_signature_error(_: E) -> FirmwareUpdaterError { + FirmwareUpdaterError::Signature(signature::Error::default()) + } + + let public_key: [u8; PUBLICKEY_SERIALIZED_LENGTH] = _public_key.try_into().map_err(into_signature_error)?; + let public_key = PublicKey::try_from(&public_key).map_err(into_signature_error)?; + let signature: [u8; SIGNATURE_SERIALIZED_LENGTH] = _signature.try_into().map_err(into_signature_error)?; + let signature = Signature::try_from(&signature).map_err(into_signature_error)?; + + let mut message = [0; 64]; + self.hash::<_, Sha512>(_state_and_dfu_flash, _update_len, _aligned, &mut message) + .await?; + + let r = public_key.verify(&message, &signature); + trace!( + "Verifying with public key {}, signature {} and message {} yields ok: {}", + public_key.to_bytes(), + signature.to_bytes(), + message, + r.is_ok() + ); + r.map_err(into_signature_error)? + } + + self.set_magic(_aligned, SWAP_MAGIC, _state_and_dfu_flash).await + } + + /// Verify the update in DFU with any digest. + pub async fn hash( + &mut self, + dfu_flash: &mut F, + update_len: u32, + chunk_buf: &mut [u8], + output: &mut [u8], + ) -> Result<(), FirmwareUpdaterError> { + let mut digest = D::new(); + for offset in (0..update_len).step_by(chunk_buf.len()) { + self.dfu.read(dfu_flash, offset, chunk_buf).await?; + let len = core::cmp::min((update_len - offset) as usize, chunk_buf.len()); + digest.update(&chunk_buf[..len]); + } + output.copy_from_slice(digest.finalize().as_slice()); + Ok(()) + } + + /// Mark to trigger firmware swap on next boot. + /// + /// # Safety + /// + /// The `aligned` buffer must have a size of F::WRITE_SIZE, and follow the alignment rules for the flash being written to. + #[cfg(all(feature = "nightly", not(feature = "_verify")))] + pub async fn mark_updated( + &mut self, + state_flash: &mut F, + aligned: &mut [u8], + ) -> Result<(), FirmwareUpdaterError> { + assert_eq!(aligned.len(), F::WRITE_SIZE); + self.set_magic(aligned, SWAP_MAGIC, state_flash).await + } + + /// Mark firmware boot successful and stop rollback on reset. + /// + /// # Safety + /// + /// The `aligned` buffer must have a size of F::WRITE_SIZE, and follow the alignment rules for the flash being written to. + pub async fn mark_booted( + &mut self, + state_flash: &mut F, + aligned: &mut [u8], + ) -> Result<(), FirmwareUpdaterError> { + assert_eq!(aligned.len(), F::WRITE_SIZE); + self.set_magic(aligned, BOOT_MAGIC, state_flash).await + } + + async fn set_magic( + &mut self, + aligned: &mut [u8], + magic: u8, + state_flash: &mut F, + ) -> Result<(), FirmwareUpdaterError> { + self.state.read(state_flash, 0, aligned).await?; + + if aligned.iter().any(|&b| b != magic) { + // Read progress validity + self.state.read(state_flash, F::WRITE_SIZE as u32, aligned).await?; + + // FIXME: Do not make this assumption. + const STATE_ERASE_VALUE: u8 = 0xFF; + + if aligned.iter().any(|&b| b != STATE_ERASE_VALUE) { + // The current progress validity marker is invalid + } else { + // Invalidate progress + aligned.fill(!STATE_ERASE_VALUE); + self.state.write(state_flash, F::WRITE_SIZE as u32, aligned).await?; + } + + // Clear magic and progress + self.state.wipe(state_flash).await?; + + // Set magic + aligned.fill(magic); + self.state.write(state_flash, 0, aligned).await?; + } + Ok(()) + } + + /// Write data to a flash page. + /// + /// The buffer must follow alignment requirements of the target flash and a multiple of page size big. + /// + /// # Safety + /// + /// Failing to meet alignment and size requirements may result in a panic. + pub async fn write_firmware( + &mut self, + offset: usize, + data: &[u8], + dfu_flash: &mut F, + ) -> Result<(), FirmwareUpdaterError> { + assert!(data.len() >= F::ERASE_SIZE); + + self.dfu + .erase(dfu_flash, offset as u32, (offset + data.len()) as u32) + .await?; + + self.dfu.write(dfu_flash, offset as u32, data).await?; + + Ok(()) + } + + /// Prepare for an incoming DFU update by erasing the entire DFU area and + /// returning its `Partition`. + /// + /// Using this instead of `write_firmware` allows for an optimized API in + /// exchange for added complexity. + pub async fn prepare_update( + &mut self, + dfu_flash: &mut F, + ) -> Result { + self.dfu.wipe(dfu_flash).await?; + + Ok(self.dfu) + } +} + +#[cfg(test)] +mod tests { + use futures::executor::block_on; + use sha1::{Digest, Sha1}; + + use super::*; + use crate::mem_flash::MemFlash; + + #[test] + fn can_verify_sha1() { + const STATE: Partition = Partition::new(0, 4096); + const DFU: Partition = Partition::new(65536, 131072); + + let mut flash = MemFlash::<131072, 4096, 8>::default(); + + let update = [0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66]; + let mut to_write = [0; 4096]; + to_write[..7].copy_from_slice(update.as_slice()); + + let mut updater = FirmwareUpdater::new(DFU, STATE); + block_on(updater.write_firmware(0, to_write.as_slice(), &mut flash)).unwrap(); + let mut chunk_buf = [0; 2]; + let mut hash = [0; 20]; + block_on(updater.hash::<_, Sha1>(&mut flash, update.len() as u32, &mut chunk_buf, &mut hash)).unwrap(); + + assert_eq!(Sha1::digest(update).as_slice(), hash); + } +} diff --git a/embassy-boot/boot/src/firmware_updater/blocking.rs b/embassy-boot/boot/src/firmware_updater/blocking.rs new file mode 100644 index 00000000..50caaf08 --- /dev/null +++ b/embassy-boot/boot/src/firmware_updater/blocking.rs @@ -0,0 +1,221 @@ +use digest::Digest; +use embedded_storage::nor_flash::NorFlash; + +use crate::{FirmwareUpdater, FirmwareUpdaterError, Partition, State, BOOT_MAGIC, SWAP_MAGIC}; + +impl FirmwareUpdater { + /// Create a firmware updater instance with partition ranges for the update and state partitions. + pub const fn new(dfu: Partition, state: Partition) -> Self { + Self { dfu, state } + } + + /// Obtain the current state. + /// + /// This is useful to check if the bootloader has just done a swap, in order + /// to do verifications and self-tests of the new image before calling + /// `mark_booted`. + pub fn get_state_blocking( + &mut self, + state_flash: &mut F, + aligned: &mut [u8], + ) -> Result { + self.state.read_blocking(state_flash, 0, aligned)?; + + if !aligned.iter().any(|&b| b != SWAP_MAGIC) { + Ok(State::Swap) + } else { + Ok(State::Boot) + } + } + + /// Verify the DFU given a public key. If there is an error then DO NOT + /// proceed with updating the firmware as it must be signed with a + /// corresponding private key (otherwise it could be malicious firmware). + /// + /// Mark to trigger firmware swap on next boot if verify suceeds. + /// + /// If the "ed25519-salty" feature is set (or another similar feature) then the signature is expected to have + /// been generated from a SHA-512 digest of the firmware bytes. + /// + /// If no signature feature is set then this method will always return a + /// signature error. + /// + /// # Safety + /// + /// The `_aligned` buffer must have a size of F::WRITE_SIZE, and follow the alignment rules for the flash being read from + /// and written to. + #[cfg(feature = "_verify")] + pub fn verify_and_mark_updated_blocking( + &mut self, + _state_and_dfu_flash: &mut F, + _public_key: &[u8], + _signature: &[u8], + _update_len: u32, + _aligned: &mut [u8], + ) -> Result<(), FirmwareUpdaterError> { + assert_eq!(_aligned.len(), F::WRITE_SIZE); + assert!(_update_len <= self.dfu.size()); + + #[cfg(feature = "ed25519-dalek")] + { + use ed25519_dalek::{PublicKey, Signature, SignatureError, Verifier}; + + use crate::digest_adapters::ed25519_dalek::Sha512; + + let into_signature_error = |e: SignatureError| FirmwareUpdaterError::Signature(e.into()); + + let public_key = PublicKey::from_bytes(_public_key).map_err(into_signature_error)?; + let signature = Signature::from_bytes(_signature).map_err(into_signature_error)?; + + let mut message = [0; 64]; + self.hash_blocking::<_, Sha512>(_state_and_dfu_flash, _update_len, _aligned, &mut message)?; + + public_key.verify(&message, &signature).map_err(into_signature_error)? + } + #[cfg(feature = "ed25519-salty")] + { + use salty::constants::{PUBLICKEY_SERIALIZED_LENGTH, SIGNATURE_SERIALIZED_LENGTH}; + use salty::{PublicKey, Signature}; + + use crate::digest_adapters::salty::Sha512; + + fn into_signature_error(_: E) -> FirmwareUpdaterError { + FirmwareUpdaterError::Signature(signature::Error::default()) + } + + let public_key: [u8; PUBLICKEY_SERIALIZED_LENGTH] = _public_key.try_into().map_err(into_signature_error)?; + let public_key = PublicKey::try_from(&public_key).map_err(into_signature_error)?; + let signature: [u8; SIGNATURE_SERIALIZED_LENGTH] = _signature.try_into().map_err(into_signature_error)?; + let signature = Signature::try_from(&signature).map_err(into_signature_error)?; + + let mut message = [0; 64]; + self.hash_blocking::<_, Sha512>(_state_and_dfu_flash, _update_len, _aligned, &mut message)?; + + let r = public_key.verify(&message, &signature); + trace!( + "Verifying with public key {}, signature {} and message {} yields ok: {}", + public_key.to_bytes(), + signature.to_bytes(), + message, + r.is_ok() + ); + r.map_err(into_signature_error)? + } + + self.set_magic_blocking(_aligned, SWAP_MAGIC, _state_and_dfu_flash) + } + + /// Verify the update in DFU with any digest. + pub fn hash_blocking( + &mut self, + dfu_flash: &mut F, + update_len: u32, + chunk_buf: &mut [u8], + output: &mut [u8], + ) -> Result<(), FirmwareUpdaterError> { + let mut digest = D::new(); + for offset in (0..update_len).step_by(chunk_buf.len()) { + self.dfu.read_blocking(dfu_flash, offset, chunk_buf)?; + let len = core::cmp::min((update_len - offset) as usize, chunk_buf.len()); + digest.update(&chunk_buf[..len]); + } + output.copy_from_slice(digest.finalize().as_slice()); + Ok(()) + } + + /// Mark to trigger firmware swap on next boot. + /// + /// # Safety + /// + /// The `aligned` buffer must have a size of F::WRITE_SIZE, and follow the alignment rules for the flash being written to. + #[cfg(not(feature = "_verify"))] + pub fn mark_updated_blocking( + &mut self, + state_flash: &mut F, + aligned: &mut [u8], + ) -> Result<(), FirmwareUpdaterError> { + assert_eq!(aligned.len(), F::WRITE_SIZE); + self.set_magic_blocking(aligned, SWAP_MAGIC, state_flash) + } + + /// Mark firmware boot successful and stop rollback on reset. + /// + /// # Safety + /// + /// The `aligned` buffer must have a size of F::WRITE_SIZE, and follow the alignment rules for the flash being written to. + pub fn mark_booted_blocking( + &mut self, + state_flash: &mut F, + aligned: &mut [u8], + ) -> Result<(), FirmwareUpdaterError> { + assert_eq!(aligned.len(), F::WRITE_SIZE); + self.set_magic_blocking(aligned, BOOT_MAGIC, state_flash) + } + + fn set_magic_blocking( + &mut self, + aligned: &mut [u8], + magic: u8, + state_flash: &mut F, + ) -> Result<(), FirmwareUpdaterError> { + self.state.read_blocking(state_flash, 0, aligned)?; + + if aligned.iter().any(|&b| b != magic) { + // Read progress validity + self.state.read_blocking(state_flash, F::WRITE_SIZE as u32, aligned)?; + + // FIXME: Do not make this assumption. + const STATE_ERASE_VALUE: u8 = 0xFF; + + if aligned.iter().any(|&b| b != STATE_ERASE_VALUE) { + // The current progress validity marker is invalid + } else { + // Invalidate progress + aligned.fill(!STATE_ERASE_VALUE); + self.state.write_blocking(state_flash, F::WRITE_SIZE as u32, aligned)?; + } + + // Clear magic and progress + self.state.wipe_blocking(state_flash)?; + + // Set magic + aligned.fill(magic); + self.state.write_blocking(state_flash, 0, aligned)?; + } + Ok(()) + } + + /// Write data to a flash page. + /// + /// The buffer must follow alignment requirements of the target flash and a multiple of page size big. + /// + /// # Safety + /// + /// Failing to meet alignment and size requirements may result in a panic. + pub fn write_firmware_blocking( + &mut self, + offset: usize, + data: &[u8], + dfu_flash: &mut F, + ) -> Result<(), FirmwareUpdaterError> { + assert!(data.len() >= F::ERASE_SIZE); + + self.dfu + .erase_blocking(dfu_flash, offset as u32, (offset + data.len()) as u32)?; + + self.dfu.write_blocking(dfu_flash, offset as u32, data)?; + + Ok(()) + } + + /// Prepare for an incoming DFU update by erasing the entire DFU area and + /// returning its `Partition`. + /// + /// Using this instead of `write_firmware_blocking` allows for an optimized + /// API in exchange for added complexity. + pub fn prepare_update_blocking(&mut self, flash: &mut F) -> Result { + self.dfu.wipe_blocking(flash)?; + + Ok(self.dfu) + } +} diff --git a/embassy-boot/boot/src/firmware_updater/mod.rs b/embassy-boot/boot/src/firmware_updater/mod.rs new file mode 100644 index 00000000..e09f5eb6 --- /dev/null +++ b/embassy-boot/boot/src/firmware_updater/mod.rs @@ -0,0 +1,71 @@ +#[cfg(feature = "nightly")] +mod asynch; +mod blocking; + +use embedded_storage::nor_flash::{NorFlashError, NorFlashErrorKind}; + +use crate::Partition; + +/// Errors returned by FirmwareUpdater +#[derive(Debug)] +pub enum FirmwareUpdaterError { + /// Error from flash. + Flash(NorFlashErrorKind), + /// Signature errors. + Signature(signature::Error), +} + +#[cfg(feature = "defmt")] +impl defmt::Format for FirmwareUpdaterError { + fn format(&self, fmt: defmt::Formatter) { + match self { + FirmwareUpdaterError::Flash(_) => defmt::write!(fmt, "FirmwareUpdaterError::Flash(_)"), + FirmwareUpdaterError::Signature(_) => defmt::write!(fmt, "FirmwareUpdaterError::Signature(_)"), + } + } +} + +impl From for FirmwareUpdaterError +where + E: NorFlashError, +{ + fn from(error: E) -> Self { + FirmwareUpdaterError::Flash(error.kind()) + } +} + +/// FirmwareUpdater is an application API for interacting with the BootLoader without the ability to +/// 'mess up' the internal bootloader state +pub struct FirmwareUpdater { + state: Partition, + dfu: Partition, +} + +#[cfg(target_os = "none")] +impl Default for FirmwareUpdater { + fn default() -> Self { + extern "C" { + static __bootloader_state_start: u32; + static __bootloader_state_end: u32; + static __bootloader_dfu_start: u32; + static __bootloader_dfu_end: u32; + } + + let dfu = unsafe { + Partition::new( + &__bootloader_dfu_start as *const u32 as u32, + &__bootloader_dfu_end as *const u32 as u32, + ) + }; + let state = unsafe { + Partition::new( + &__bootloader_state_start as *const u32 as u32, + &__bootloader_state_end as *const u32 as u32, + ) + }; + + trace!("DFU: 0x{:x} - 0x{:x}", dfu.from, dfu.to); + trace!("STATE: 0x{:x} - 0x{:x}", state.from, state.to); + FirmwareUpdater::new(dfu, state) + } +} From aba0f8fd6cd51cad65480689bc9254df4f071175 Mon Sep 17 00:00:00 2001 From: xoviat Date: Mon, 29 May 2023 14:49:43 -0500 Subject: [PATCH 153/178] stm32/uart: refactor rx ringbuffer - remove some race conditions - allow full use of rx buffer --- .vscode/.gitignore | 3 +- embassy-stm32/src/dma/bdma.rs | 40 +- embassy-stm32/src/dma/dma.rs | 40 +- embassy-stm32/src/dma/ringbuffer.rs | 529 ++++++++++-------- embassy-stm32/src/lib.rs | 2 +- embassy-stm32/src/usart/mod.rs | 50 +- .../{rx_ringbuffered.rs => ringbuffered.rs} | 182 +++--- 7 files changed, 422 insertions(+), 424 deletions(-) rename embassy-stm32/src/usart/{rx_ringbuffered.rs => ringbuffered.rs} (63%) diff --git a/.vscode/.gitignore b/.vscode/.gitignore index 9fbb9ec9..8c3dd8a3 100644 --- a/.vscode/.gitignore +++ b/.vscode/.gitignore @@ -1,3 +1,4 @@ *.cortex-debug.*.json launch.json -tasks.json \ No newline at end of file +tasks.json +*.cfg diff --git a/embassy-stm32/src/dma/bdma.rs b/embassy-stm32/src/dma/bdma.rs index 0202ec37..9dafa26d 100644 --- a/embassy-stm32/src/dma/bdma.rs +++ b/embassy-stm32/src/dma/bdma.rs @@ -111,24 +111,18 @@ pub(crate) unsafe fn on_irq_inner(dma: pac::bdma::Dma, channel_num: usize, index panic!("DMA: error on BDMA@{:08x} channel {}", dma.0 as u32, channel_num); } - let mut wake = false; - if isr.htif(channel_num) && cr.read().htie() { // Acknowledge half transfer complete interrupt dma.ifcr().write(|w| w.set_htif(channel_num, true)); - wake = true; - } - - if isr.tcif(channel_num) && cr.read().tcie() { + } else if isr.tcif(channel_num) && cr.read().tcie() { // Acknowledge transfer complete interrupt dma.ifcr().write(|w| w.set_tcif(channel_num, true)); STATE.complete_count[index].fetch_add(1, Ordering::Release); - wake = true; + } else { + return; } - if wake { - STATE.ch_wakers[index].wake(); - } + STATE.ch_wakers[index].wake(); } #[cfg(any(bdma_v2, dmamux))] @@ -371,7 +365,7 @@ impl<'a, C: Channel> Future for Transfer<'a, C> { struct DmaCtrlImpl<'a, C: Channel>(PeripheralRef<'a, C>); impl<'a, C: Channel> DmaCtrl for DmaCtrlImpl<'a, C> { - fn ndtr(&self) -> usize { + fn get_remaining_transfers(&self) -> usize { let ch = self.0.regs().ch(self.0.num()); unsafe { ch.ndtr().read() }.ndt() as usize } @@ -457,21 +451,17 @@ impl<'a, C: Channel, W: Word> RingBuffer<'a, C, W> { } /// Read bytes from the ring buffer + /// Return a tuple of the length read and the length remaining in the buffer + /// If not all of the bytes were read, then there will be some bytes in the buffer remaining + /// The length remaining is the capacity, ring_buf.len(), less the bytes remaining after the read /// OverrunError is returned if the portion to be read was overwritten by the DMA controller. - pub fn read(&mut self, buf: &mut [W]) -> Result { + pub fn read(&mut self, buf: &mut [W]) -> Result<(usize, usize), OverrunError> { self.ringbuf.read(DmaCtrlImpl(self.channel.reborrow()), buf) } - pub fn is_empty(&self) -> bool { - self.ringbuf.is_empty() - } - - pub fn len(&self) -> usize { - self.ringbuf.len() - } - - pub fn capacity(&self) -> usize { - self.ringbuf.dma_buf.len() + /// The capacity of the ringbuffer + pub fn cap(&self) -> usize { + self.ringbuf.cap() } pub fn set_waker(&mut self, waker: &Waker) { @@ -506,12 +496,6 @@ impl<'a, C: Channel, W: Word> RingBuffer<'a, C, W> { let ch = self.channel.regs().ch(self.channel.num()); unsafe { ch.cr().read() }.en() } - - /// Synchronize the position of the ring buffer to the actual DMA controller position - pub fn reload_position(&mut self) { - let ch = self.channel.regs().ch(self.channel.num()); - self.ringbuf.ndtr = unsafe { ch.ndtr().read() }.ndt() as usize; - } } impl<'a, C: Channel, W: Word> Drop for RingBuffer<'a, C, W> { diff --git a/embassy-stm32/src/dma/dma.rs b/embassy-stm32/src/dma/dma.rs index 7b17d9e4..47b749ec 100644 --- a/embassy-stm32/src/dma/dma.rs +++ b/embassy-stm32/src/dma/dma.rs @@ -187,24 +187,18 @@ pub(crate) unsafe fn on_irq_inner(dma: pac::dma::Dma, channel_num: usize, index: panic!("DMA: error on DMA@{:08x} channel {}", dma.0 as u32, channel_num); } - let mut wake = false; - if isr.htif(channel_num % 4) && cr.read().htie() { // Acknowledge half transfer complete interrupt dma.ifcr(channel_num / 4).write(|w| w.set_htif(channel_num % 4, true)); - wake = true; - } - - if isr.tcif(channel_num % 4) && cr.read().tcie() { + } else if isr.tcif(channel_num % 4) && cr.read().tcie() { // Acknowledge transfer complete interrupt dma.ifcr(channel_num / 4).write(|w| w.set_tcif(channel_num % 4, true)); STATE.complete_count[index].fetch_add(1, Ordering::Release); - wake = true; + } else { + return; } - if wake { - STATE.ch_wakers[index].wake(); - } + STATE.ch_wakers[index].wake(); } #[cfg(any(dma_v2, dmamux))] @@ -612,7 +606,7 @@ impl<'a, C: Channel, W: Word> Drop for DoubleBuffered<'a, C, W> { struct DmaCtrlImpl<'a, C: Channel>(PeripheralRef<'a, C>); impl<'a, C: Channel> DmaCtrl for DmaCtrlImpl<'a, C> { - fn ndtr(&self) -> usize { + fn get_remaining_transfers(&self) -> usize { let ch = self.0.regs().st(self.0.num()); unsafe { ch.ndtr().read() }.ndt() as usize } @@ -713,21 +707,17 @@ impl<'a, C: Channel, W: Word> RingBuffer<'a, C, W> { } /// Read bytes from the ring buffer + /// Return a tuple of the length read and the length remaining in the buffer + /// If not all of the bytes were read, then there will be some bytes in the buffer remaining + /// The length remaining is the capacity, ring_buf.len(), less the bytes remaining after the read /// OverrunError is returned if the portion to be read was overwritten by the DMA controller. - pub fn read(&mut self, buf: &mut [W]) -> Result { + pub fn read(&mut self, buf: &mut [W]) -> Result<(usize, usize), OverrunError> { self.ringbuf.read(DmaCtrlImpl(self.channel.reborrow()), buf) } - pub fn is_empty(&self) -> bool { - self.ringbuf.is_empty() - } - - pub fn len(&self) -> usize { - self.ringbuf.len() - } - - pub fn capacity(&self) -> usize { - self.ringbuf.dma_buf.len() + // The capacity of the ringbuffer + pub fn cap(&self) -> usize { + self.ringbuf.cap() } pub fn set_waker(&mut self, waker: &Waker) { @@ -766,12 +756,6 @@ impl<'a, C: Channel, W: Word> RingBuffer<'a, C, W> { let ch = self.channel.regs().st(self.channel.num()); unsafe { ch.cr().read() }.en() } - - /// Synchronize the position of the ring buffer to the actual DMA controller position - pub fn reload_position(&mut self) { - let ch = self.channel.regs().st(self.channel.num()); - self.ringbuf.ndtr = unsafe { ch.ndtr().read() }.ndt() as usize; - } } impl<'a, C: Channel, W: Word> Drop for RingBuffer<'a, C, W> { diff --git a/embassy-stm32/src/dma/ringbuffer.rs b/embassy-stm32/src/dma/ringbuffer.rs index 38cc87ae..72a84a57 100644 --- a/embassy-stm32/src/dma/ringbuffer.rs +++ b/embassy-stm32/src/dma/ringbuffer.rs @@ -25,14 +25,13 @@ use super::word::Word; /// +-----------------------------------------+ +-----------------------------------------+ /// ^ ^ ^ ^ ^ ^ /// | | | | | | -/// +- first --+ | +- end ------+ | +/// +- start --+ | +- end ------+ | /// | | | | -/// +- end --------------------+ +- first ----------------+ +/// +- end --------------------+ +- start ----------------+ /// ``` pub struct DmaRingBuffer<'a, W: Word> { pub(crate) dma_buf: &'a mut [W], - first: usize, - pub ndtr: usize, + start: usize, } #[derive(Debug, PartialEq)] @@ -41,7 +40,7 @@ pub struct OverrunError; pub trait DmaCtrl { /// Get the NDTR register value, i.e. the space left in the underlying /// buffer until the dma writer wraps. - fn ndtr(&self) -> usize; + fn get_remaining_transfers(&self) -> usize; /// Get the transfer completed counter. /// This counter is incremented by the dma controller when NDTR is reloaded, @@ -54,151 +53,131 @@ pub trait DmaCtrl { impl<'a, W: Word> DmaRingBuffer<'a, W> { pub fn new(dma_buf: &'a mut [W]) -> Self { - let ndtr = dma_buf.len(); - Self { - dma_buf, - first: 0, - ndtr, - } + Self { dma_buf, start: 0 } } /// Reset the ring buffer to its initial state pub fn clear(&mut self, mut dma: impl DmaCtrl) { - self.first = 0; - self.ndtr = self.dma_buf.len(); + self.start = 0; dma.reset_complete_count(); } - /// The buffer end position - fn end(&self) -> usize { - self.dma_buf.len() - self.ndtr + /// The capacity of the ringbuffer + pub const fn cap(&self) -> usize { + self.dma_buf.len() } - /// Returns whether the buffer is empty - pub fn is_empty(&self) -> bool { - self.first == self.end() - } - - /// The current number of bytes in the buffer - /// This may change at any time if dma is currently active - pub fn len(&self) -> usize { - // Read out a stable end (the dma periheral can change it at anytime) - let end = self.end(); - if self.first <= end { - // No wrap - end - self.first - } else { - self.dma_buf.len() - self.first + end - } + /// The current position of the ringbuffer + fn pos(&self, remaining_transfers: usize) -> usize { + self.cap() - remaining_transfers } /// Read bytes from the ring buffer + /// Return a tuple of the length read and the length remaining in the buffer + /// If not all of the bytes were read, then there will be some bytes in the buffer remaining + /// The length remaining is the capacity, ring_buf.len(), less the bytes remaining after the read /// OverrunError is returned if the portion to be read was overwritten by the DMA controller. - pub fn read(&mut self, mut dma: impl DmaCtrl, buf: &mut [W]) -> Result { - let end = self.end(); + pub fn read(&mut self, mut dma: impl DmaCtrl, buf: &mut [W]) -> Result<(usize, usize), OverrunError> { + /* + This algorithm is optimistic: we assume we haven't overrun more than a full buffer and then check + after we've done our work to see we have. This is because on stm32, an interrupt is not guaranteed + to fire in the same clock cycle that a register is read, so checking get_complete_count early does + not yield relevant information. - compiler_fence(Ordering::SeqCst); + Therefore, the only variable we really need to know is ndtr. If the dma has overrun by more than a full + buffer, we will do a bit more work than we have to, but algorithms should not be optimized for error + conditions. - if self.first == end { - // The buffer is currently empty - - if dma.get_complete_count() > 0 { - // The DMA has written such that the ring buffer wraps at least once - self.ndtr = dma.ndtr(); - if self.end() > self.first || dma.get_complete_count() > 1 { - return Err(OverrunError); - } - } - - Ok(0) - } else if self.first < end { + After we've done our work, we confirm that we haven't overrun more than a full buffer, and also that + the dma has not overrun within the data we could have copied. We check the data we could have copied + rather than the data we actually copied because it costs nothing and confirms an error condition + earlier. + */ + let end = self.pos(dma.get_remaining_transfers()); + if self.start == end && dma.get_complete_count() == 0 { + // No bytes are available in the buffer + Ok((0, self.cap())) + } else if self.start < end { // The available, unread portion in the ring buffer DOES NOT wrap - - if dma.get_complete_count() > 1 { - return Err(OverrunError); - } - // Copy out the bytes from the dma buffer - let len = self.copy_to(buf, self.first..end); + let len = self.copy_to(buf, self.start..end); compiler_fence(Ordering::SeqCst); - match dma.get_complete_count() { - 0 => { - // The DMA writer has not wrapped before nor after the copy - } - 1 => { - // The DMA writer has written such that the ring buffer now wraps - self.ndtr = dma.ndtr(); - if self.end() > self.first || dma.get_complete_count() > 1 { - // The bytes that we have copied out have overflowed - // as the writer has now both wrapped and is currently writing - // within the region that we have just copied out - return Err(OverrunError); - } - } - _ => { - return Err(OverrunError); - } - } + /* + first, check if the dma has wrapped at all if it's after end + or more than once if it's before start - self.first = (self.first + len) % self.dma_buf.len(); - Ok(len) + this is in a critical section to try to reduce mushy behavior. + it's not ideal but it's the best we can do + + then, get the current position of of the dma write and check + if it's inside data we could have copied + */ + let (pos, complete_count) = + critical_section::with(|_| (self.pos(dma.get_remaining_transfers()), dma.get_complete_count())); + if (pos >= self.start && pos < end) || (complete_count > 0 && pos >= end) || complete_count > 1 { + Err(OverrunError) + } else { + self.start = (self.start + len) % self.cap(); + + Ok((len, self.cap() - self.start)) + } + } else if self.start + buf.len() < self.cap() { + // The available, unread portion in the ring buffer DOES wrap + // The DMA writer has wrapped since we last read and is currently + // writing (or the next byte added will be) in the beginning of the ring buffer. + + // The provided read buffer is not large enough to include all bytes from the tail of the dma buffer. + + // Copy out from the dma buffer + let len = self.copy_to(buf, self.start..self.cap()); + + compiler_fence(Ordering::SeqCst); + + /* + first, check if the dma has wrapped around more than once + + then, get the current position of of the dma write and check + if it's inside data we could have copied + */ + let pos = self.pos(dma.get_remaining_transfers()); + if pos > self.start || pos < end || dma.get_complete_count() > 1 { + Err(OverrunError) + } else { + self.start = (self.start + len) % self.cap(); + + Ok((len, self.start + end)) + } } else { // The available, unread portion in the ring buffer DOES wrap // The DMA writer has wrapped since we last read and is currently // writing (or the next byte added will be) in the beginning of the ring buffer. - let complete_count = dma.get_complete_count(); - if complete_count > 1 { - return Err(OverrunError); - } + // The provided read buffer is large enough to include all bytes from the tail of the dma buffer, + // so the next read will not have any unread tail bytes in the ring buffer. - // If the unread portion wraps then the writer must also have wrapped - assert!(complete_count == 1); + // Copy out from the dma buffer + let tail = self.copy_to(buf, self.start..self.cap()); + let head = self.copy_to(&mut buf[tail..], 0..end); - if self.first + buf.len() < self.dma_buf.len() { - // The provided read buffer is not large enough to include all bytes from the tail of the dma buffer. + compiler_fence(Ordering::SeqCst); - // Copy out from the dma buffer - let len = self.copy_to(buf, self.first..self.dma_buf.len()); + /* + first, check if the dma has wrapped around more than once - compiler_fence(Ordering::SeqCst); - - // We have now copied out the data from dma_buf - // Make sure that the just read part was not overwritten during the copy - self.ndtr = dma.ndtr(); - if self.end() > self.first || dma.get_complete_count() > 1 { - // The writer has entered the data that we have just read since we read out `end` in the beginning and until now. - return Err(OverrunError); - } - - self.first = (self.first + len) % self.dma_buf.len(); - Ok(len) + then, get the current position of of the dma write and check + if it's inside data we could have copied + */ + let pos = self.pos(dma.get_remaining_transfers()); + if pos > self.start || pos < end || dma.reset_complete_count() > 1 { + Err(OverrunError) } else { - // The provided read buffer is large enough to include all bytes from the tail of the dma buffer, - // so the next read will not have any unread tail bytes in the ring buffer. - - // Copy out from the dma buffer - let tail = self.copy_to(buf, self.first..self.dma_buf.len()); - let head = self.copy_to(&mut buf[tail..], 0..end); - - compiler_fence(Ordering::SeqCst); - - // We have now copied out the data from dma_buf - // Reset complete counter and make sure that the just read part was not overwritten during the copy - self.ndtr = dma.ndtr(); - let complete_count = dma.reset_complete_count(); - if self.end() > self.first || complete_count > 1 { - return Err(OverrunError); - } - - self.first = head; - Ok(tail + head) + self.start = head; + Ok((tail + head, self.cap() - self.start)) } } } - /// Copy from the dma buffer at `data_range` into `buf` fn copy_to(&mut self, buf: &mut [W], data_range: Range) -> usize { // Limit the number of bytes that can be copied @@ -218,203 +197,289 @@ impl<'a, W: Word> DmaRingBuffer<'a, W> { length } } - #[cfg(test)] mod tests { use core::array; - use core::cell::RefCell; + use std::{cell, vec}; use super::*; - struct TestCtrl { - next_ndtr: RefCell>, - complete_count: usize, + #[allow(dead_code)] + #[derive(PartialEq, Debug)] + enum TestCircularTransferRequest { + GetCompleteCount(usize), + ResetCompleteCount(usize), + PositionRequest(usize), } - impl TestCtrl { - pub const fn new() -> Self { - Self { - next_ndtr: RefCell::new(None), - complete_count: 0, + struct TestCircularTransfer { + len: usize, + requests: cell::RefCell>, + } + + impl DmaCtrl for &mut TestCircularTransfer { + fn get_remaining_transfers(&self) -> usize { + match self.requests.borrow_mut().pop().unwrap() { + TestCircularTransferRequest::PositionRequest(pos) => { + let len = self.len; + + assert!(len >= pos); + + len - pos + } + _ => unreachable!(), } } - pub fn set_next_ndtr(&mut self, ndtr: usize) { - self.next_ndtr.borrow_mut().replace(ndtr); - } - } - - impl DmaCtrl for &mut TestCtrl { - fn ndtr(&self) -> usize { - self.next_ndtr.borrow_mut().unwrap() - } - fn get_complete_count(&self) -> usize { - self.complete_count + match self.requests.borrow_mut().pop().unwrap() { + TestCircularTransferRequest::GetCompleteCount(complete_count) => complete_count, + _ => unreachable!(), + } } fn reset_complete_count(&mut self) -> usize { - let old = self.complete_count; - self.complete_count = 0; - old + match self.requests.get_mut().pop().unwrap() { + TestCircularTransferRequest::ResetCompleteCount(complete_count) => complete_count, + _ => unreachable!(), + } + } + } + + impl TestCircularTransfer { + pub fn new(len: usize) -> Self { + Self { + requests: cell::RefCell::new(vec![]), + len: len, + } + } + + pub fn setup(&self, mut requests: vec::Vec) { + requests.reverse(); + self.requests.replace(requests); } } #[test] - fn empty() { + fn empty_and_read_not_started() { let mut dma_buf = [0u8; 16]; let ringbuf = DmaRingBuffer::new(&mut dma_buf); - assert!(ringbuf.is_empty()); - assert_eq!(0, ringbuf.len()); + assert_eq!(0, ringbuf.start); } #[test] fn can_read() { + let mut dma = TestCircularTransfer::new(16); + let mut dma_buf: [u8; 16] = array::from_fn(|idx| idx as u8); // 0, 1, ..., 15 - let mut ctrl = TestCtrl::new(); let mut ringbuf = DmaRingBuffer::new(&mut dma_buf); - ringbuf.ndtr = 6; - assert!(!ringbuf.is_empty()); - assert_eq!(10, ringbuf.len()); + assert_eq!(0, ringbuf.start); + assert_eq!(16, ringbuf.len()); + dma.setup(vec![ + TestCircularTransferRequest::PositionRequest(8), + TestCircularTransferRequest::PositionRequest(10), + TestCircularTransferRequest::GetCompleteCount(0), + ]); let mut buf = [0; 2]; - assert_eq!(2, ringbuf.read(&mut ctrl, &mut buf).unwrap()); + assert_eq!(2, ringbuf.read(&mut dma, &mut buf).unwrap().0); assert_eq!([0, 1], buf); - assert_eq!(8, ringbuf.len()); + assert_eq!(2, ringbuf.start); + dma.setup(vec![ + TestCircularTransferRequest::PositionRequest(10), + TestCircularTransferRequest::PositionRequest(12), + TestCircularTransferRequest::GetCompleteCount(0), + ]); let mut buf = [0; 2]; - assert_eq!(2, ringbuf.read(&mut ctrl, &mut buf).unwrap()); + assert_eq!(2, ringbuf.read(&mut dma, &mut buf).unwrap().0); assert_eq!([2, 3], buf); - assert_eq!(6, ringbuf.len()); + assert_eq!(4, ringbuf.start); + dma.setup(vec![ + TestCircularTransferRequest::PositionRequest(12), + TestCircularTransferRequest::PositionRequest(14), + TestCircularTransferRequest::GetCompleteCount(0), + ]); let mut buf = [0; 8]; - assert_eq!(6, ringbuf.read(&mut ctrl, &mut buf).unwrap()); + assert_eq!(8, ringbuf.read(&mut dma, &mut buf).unwrap().0); assert_eq!([4, 5, 6, 7, 8, 9], buf[..6]); - assert_eq!(0, ringbuf.len()); - - let mut buf = [0; 2]; - assert_eq!(0, ringbuf.read(&mut ctrl, &mut buf).unwrap()); + assert_eq!(12, ringbuf.start); } #[test] fn can_read_with_wrap() { + let mut dma = TestCircularTransfer::new(16); + let mut dma_buf: [u8; 16] = array::from_fn(|idx| idx as u8); // 0, 1, ..., 15 - let mut ctrl = TestCtrl::new(); let mut ringbuf = DmaRingBuffer::new(&mut dma_buf); - ringbuf.first = 12; - ringbuf.ndtr = 10; - // The dma controller has written 4 + 6 bytes and has reloaded NDTR - ctrl.complete_count = 1; - ctrl.set_next_ndtr(10); + assert_eq!(0, ringbuf.start); + assert_eq!(16, ringbuf.len()); - assert!(!ringbuf.is_empty()); - assert_eq!(6 + 4, ringbuf.len()); + /* + Read to close to the end of the buffer + */ + dma.setup(vec![ + TestCircularTransferRequest::PositionRequest(14), + TestCircularTransferRequest::PositionRequest(16), + TestCircularTransferRequest::GetCompleteCount(0), + ]); + let mut buf = [0; 14]; + assert_eq!(14, ringbuf.read(&mut dma, &mut buf).unwrap().0); + assert_eq!(14, ringbuf.start); - let mut buf = [0; 2]; - assert_eq!(2, ringbuf.read(&mut ctrl, &mut buf).unwrap()); - assert_eq!([12, 13], buf); - assert_eq!(6 + 2, ringbuf.len()); - - let mut buf = [0; 4]; - assert_eq!(4, ringbuf.read(&mut ctrl, &mut buf).unwrap()); - assert_eq!([14, 15, 0, 1], buf); - assert_eq!(4, ringbuf.len()); + /* + Now, read around the buffer + */ + dma.setup(vec![ + TestCircularTransferRequest::PositionRequest(6), + TestCircularTransferRequest::PositionRequest(8), + TestCircularTransferRequest::ResetCompleteCount(1), + ]); + let mut buf = [0; 6]; + assert_eq!(6, ringbuf.read(&mut dma, &mut buf).unwrap().0); + assert_eq!(4, ringbuf.start); } #[test] fn can_read_when_dma_writer_is_wrapped_and_read_does_not_wrap() { + let mut dma = TestCircularTransfer::new(16); + let mut dma_buf: [u8; 16] = array::from_fn(|idx| idx as u8); // 0, 1, ..., 15 - let mut ctrl = TestCtrl::new(); let mut ringbuf = DmaRingBuffer::new(&mut dma_buf); - ringbuf.first = 2; - ringbuf.ndtr = 6; - // The dma controller has written 6 + 2 bytes and has reloaded NDTR - ctrl.complete_count = 1; - ctrl.set_next_ndtr(14); + assert_eq!(0, ringbuf.start); + assert_eq!(16, ringbuf.len()); + /* + Read to close to the end of the buffer + */ + dma.setup(vec![ + TestCircularTransferRequest::PositionRequest(14), + TestCircularTransferRequest::PositionRequest(16), + TestCircularTransferRequest::GetCompleteCount(0), + ]); + let mut buf = [0; 14]; + assert_eq!(14, ringbuf.read(&mut dma, &mut buf).unwrap().0); + assert_eq!(14, ringbuf.start); + + /* + Now, read to the end of the buffer + */ + dma.setup(vec![ + TestCircularTransferRequest::PositionRequest(6), + TestCircularTransferRequest::PositionRequest(8), + TestCircularTransferRequest::ResetCompleteCount(1), + ]); let mut buf = [0; 2]; - assert_eq!(2, ringbuf.read(&mut ctrl, &mut buf).unwrap()); - assert_eq!([2, 3], buf); - - assert_eq!(1, ctrl.complete_count); // The interrupt flag IS NOT cleared + assert_eq!(2, ringbuf.read(&mut dma, &mut buf).unwrap().0); + assert_eq!(0, ringbuf.start); } #[test] - fn can_read_when_dma_writer_is_wrapped_and_read_wraps() { + fn can_read_when_dma_writer_wraps_once_with_same_ndtr() { + let mut dma = TestCircularTransfer::new(16); + let mut dma_buf: [u8; 16] = array::from_fn(|idx| idx as u8); // 0, 1, ..., 15 - let mut ctrl = TestCtrl::new(); let mut ringbuf = DmaRingBuffer::new(&mut dma_buf); - ringbuf.first = 12; - ringbuf.ndtr = 10; - // The dma controller has written 6 + 2 bytes and has reloaded NDTR - ctrl.complete_count = 1; - ctrl.set_next_ndtr(14); + assert_eq!(0, ringbuf.start); + assert_eq!(16, ringbuf.len()); - let mut buf = [0; 10]; - assert_eq!(10, ringbuf.read(&mut ctrl, &mut buf).unwrap()); - assert_eq!([12, 13, 14, 15, 0, 1, 2, 3, 4, 5], buf); + /* + Read to about the middle of the buffer + */ + dma.setup(vec![ + TestCircularTransferRequest::PositionRequest(6), + TestCircularTransferRequest::PositionRequest(6), + TestCircularTransferRequest::GetCompleteCount(0), + ]); + let mut buf = [0; 6]; + assert_eq!(6, ringbuf.read(&mut dma, &mut buf).unwrap().0); + assert_eq!(6, ringbuf.start); - assert_eq!(0, ctrl.complete_count); // The interrupt flag IS cleared - } - - #[test] - fn cannot_read_when_dma_writer_wraps_with_same_ndtr() { - let mut dma_buf = [0u8; 16]; - let mut ctrl = TestCtrl::new(); - let mut ringbuf = DmaRingBuffer::new(&mut dma_buf); - ringbuf.first = 6; - ringbuf.ndtr = 10; - ctrl.set_next_ndtr(9); - - assert!(ringbuf.is_empty()); // The ring buffer thinks that it is empty - - // The dma controller has written exactly 16 bytes - ctrl.complete_count = 1; - - let mut buf = [0; 2]; - assert_eq!(Err(OverrunError), ringbuf.read(&mut ctrl, &mut buf)); - - assert_eq!(1, ctrl.complete_count); // The complete counter is not reset + /* + Now, wrap the DMA controller around + */ + dma.setup(vec![ + TestCircularTransferRequest::PositionRequest(6), + TestCircularTransferRequest::GetCompleteCount(1), + TestCircularTransferRequest::PositionRequest(6), + TestCircularTransferRequest::GetCompleteCount(1), + ]); + let mut buf = [0; 6]; + assert_eq!(6, ringbuf.read(&mut dma, &mut buf).unwrap().0); + assert_eq!(12, ringbuf.start); } #[test] fn cannot_read_when_dma_writer_overwrites_during_not_wrapping_read() { + let mut dma = TestCircularTransfer::new(16); + let mut dma_buf: [u8; 16] = array::from_fn(|idx| idx as u8); // 0, 1, ..., 15 - let mut ctrl = TestCtrl::new(); let mut ringbuf = DmaRingBuffer::new(&mut dma_buf); - ringbuf.first = 2; - ringbuf.ndtr = 6; - // The dma controller has written 6 + 3 bytes and has reloaded NDTR - ctrl.complete_count = 1; - ctrl.set_next_ndtr(13); + assert_eq!(0, ringbuf.start); + assert_eq!(16, ringbuf.len()); - let mut buf = [0; 2]; - assert_eq!(Err(OverrunError), ringbuf.read(&mut ctrl, &mut buf)); + /* + Read a few bytes + */ + dma.setup(vec![ + TestCircularTransferRequest::PositionRequest(2), + TestCircularTransferRequest::PositionRequest(2), + TestCircularTransferRequest::GetCompleteCount(0), + ]); + let mut buf = [0; 6]; + assert_eq!(2, ringbuf.read(&mut dma, &mut buf).unwrap().0); + assert_eq!(2, ringbuf.start); - assert_eq!(1, ctrl.complete_count); // The complete counter is not reset + /* + Now, overtake the reader + */ + dma.setup(vec![ + TestCircularTransferRequest::PositionRequest(4), + TestCircularTransferRequest::PositionRequest(6), + TestCircularTransferRequest::GetCompleteCount(1), + ]); + let mut buf = [0; 6]; + assert_eq!(OverrunError, ringbuf.read(&mut dma, &mut buf).unwrap_err()); } #[test] fn cannot_read_when_dma_writer_overwrites_during_wrapping_read() { + let mut dma = TestCircularTransfer::new(16); + let mut dma_buf: [u8; 16] = array::from_fn(|idx| idx as u8); // 0, 1, ..., 15 - let mut ctrl = TestCtrl::new(); let mut ringbuf = DmaRingBuffer::new(&mut dma_buf); - ringbuf.first = 12; - ringbuf.ndtr = 10; - // The dma controller has written 6 + 13 bytes and has reloaded NDTR - ctrl.complete_count = 1; - ctrl.set_next_ndtr(3); + assert_eq!(0, ringbuf.start); + assert_eq!(16, ringbuf.len()); - let mut buf = [0; 2]; - assert_eq!(Err(OverrunError), ringbuf.read(&mut ctrl, &mut buf)); + /* + Read to close to the end of the buffer + */ + dma.setup(vec![ + TestCircularTransferRequest::PositionRequest(14), + TestCircularTransferRequest::PositionRequest(16), + TestCircularTransferRequest::GetCompleteCount(0), + ]); + let mut buf = [0; 14]; + assert_eq!(14, ringbuf.read(&mut dma, &mut buf).unwrap().0); + assert_eq!(14, ringbuf.start); - assert_eq!(1, ctrl.complete_count); // The complete counter is not reset + /* + Now, overtake the reader + */ + dma.setup(vec![ + TestCircularTransferRequest::PositionRequest(8), + TestCircularTransferRequest::PositionRequest(10), + TestCircularTransferRequest::ResetCompleteCount(2), + ]); + let mut buf = [0; 6]; + assert_eq!(OverrunError, ringbuf.read(&mut dma, &mut buf).unwrap_err()); } } diff --git a/embassy-stm32/src/lib.rs b/embassy-stm32/src/lib.rs index c9df5c1b..1258a65f 100644 --- a/embassy-stm32/src/lib.rs +++ b/embassy-stm32/src/lib.rs @@ -1,4 +1,4 @@ -#![no_std] +#![cfg_attr(not(test), no_std)] #![cfg_attr(feature = "nightly", feature(async_fn_in_trait, impl_trait_projections))] // This must go FIRST so that all the other modules see its macros. diff --git a/embassy-stm32/src/usart/mod.rs b/embassy-stm32/src/usart/mod.rs index 061c859a..05ccb874 100644 --- a/embassy-stm32/src/usart/mod.rs +++ b/embassy-stm32/src/usart/mod.rs @@ -13,6 +13,12 @@ use futures::future::{select, Either}; use crate::dma::{NoDma, Transfer}; use crate::gpio::sealed::AFType; #[cfg(not(any(usart_v1, usart_v2)))] +#[allow(unused_imports)] +use crate::pac::usart::regs::Isr as Sr; +#[cfg(any(usart_v1, usart_v2))] +#[allow(unused_imports)] +use crate::pac::usart::regs::Sr; +#[cfg(not(any(usart_v1, usart_v2)))] use crate::pac::usart::Lpuart as Regs; #[cfg(any(usart_v1, usart_v2))] use crate::pac::usart::Usart as Regs; @@ -32,7 +38,6 @@ impl interrupt::Handler for InterruptHandler let (sr, cr1, cr3) = unsafe { (sr(r).read(), r.cr1().read(), r.cr3().read()) }; - let mut wake = false; let has_errors = (sr.pe() && cr1.peie()) || ((sr.fe() || sr.ne() || sr.ore()) && cr3.eie()); if has_errors { // clear all interrupts and DMA Rx Request @@ -52,35 +57,24 @@ impl interrupt::Handler for InterruptHandler w.set_dmar(false); }); } + } else if cr1.idleie() && sr.idle() { + // IDLE detected: no more data will come + unsafe { + r.cr1().modify(|w| { + // disable idle line detection + w.set_idleie(false); + }); + } + } else if cr1.rxneie() { + // We cannot check the RXNE flag as it is auto-cleared by the DMA controller - wake = true; + // It is up to the listener to determine if this in fact was a RX event and disable the RXNE detection } else { - if cr1.idleie() && sr.idle() { - // IDLE detected: no more data will come - unsafe { - r.cr1().modify(|w| { - // disable idle line detection - w.set_idleie(false); - }); - } - - wake = true; - } - - if cr1.rxneie() { - // We cannot check the RXNE flag as it is auto-cleared by the DMA controller - - // It is up to the listener to determine if this in fact was a RX event and disable the RXNE detection - - wake = true; - } + return; } - if wake { - compiler_fence(Ordering::SeqCst); - - s.rx_waker.wake(); - } + compiler_fence(Ordering::SeqCst); + s.rx_waker.wake(); } } @@ -1109,9 +1103,9 @@ pub use crate::usart::buffered::InterruptHandler as BufferedInterruptHandler; mod buffered; #[cfg(not(gpdma))] -mod rx_ringbuffered; +mod ringbuffered; #[cfg(not(gpdma))] -pub use rx_ringbuffered::RingBufferedUartRx; +pub use ringbuffered::RingBufferedUartRx; use self::sealed::Kind; diff --git a/embassy-stm32/src/usart/rx_ringbuffered.rs b/embassy-stm32/src/usart/ringbuffered.rs similarity index 63% rename from embassy-stm32/src/usart/rx_ringbuffered.rs rename to embassy-stm32/src/usart/ringbuffered.rs index 33b75049..511b71c7 100644 --- a/embassy-stm32/src/usart/rx_ringbuffered.rs +++ b/embassy-stm32/src/usart/ringbuffered.rs @@ -2,13 +2,12 @@ use core::future::poll_fn; use core::sync::atomic::{compiler_fence, Ordering}; use core::task::Poll; -use embassy_hal_common::drop::OnDrop; use embassy_hal_common::PeripheralRef; use futures::future::{select, Either}; use super::{clear_interrupt_flags, rdr, sr, BasicInstance, Error, UartRx}; -use crate::dma::ringbuffer::OverrunError; use crate::dma::RingBuffer; +use crate::usart::{Regs, Sr}; pub struct RingBufferedUartRx<'d, T: BasicInstance, RxDma: super::RxDma> { _peri: PeripheralRef<'d, T>, @@ -24,7 +23,9 @@ impl<'d, T: BasicInstance, RxDma: super::RxDma> UartRx<'d, T, RxDma> { let request = self.rx_dma.request(); let opts = Default::default(); + let ring_buf = unsafe { RingBuffer::new_read(self.rx_dma, request, rdr(T::regs()), dma_buf, opts) }; + RingBufferedUartRx { _peri: self._peri, ring_buf, @@ -42,11 +43,18 @@ impl<'d, T: BasicInstance, RxDma: super::RxDma> RingBufferedUartRx<'d, T, RxD Ok(()) } + fn stop(&mut self, err: Error) -> Result { + self.teardown_uart(); + + Err(err) + } + /// Start uart background receive fn setup_uart(&mut self) { // fence before starting DMA. compiler_fence(Ordering::SeqCst); + // start the dma controller self.ring_buf.start(); let r = T::regs(); @@ -58,8 +66,8 @@ impl<'d, T: BasicInstance, RxDma: super::RxDma> RingBufferedUartRx<'d, T, RxD w.set_rxneie(false); // enable parity interrupt if not ParityNone w.set_peie(w.pce()); - // disable idle line interrupt - w.set_idleie(false); + // enable idle line interrupt + w.set_idleie(true); }); r.cr3().modify(|w| { // enable Error Interrupt: (Frame error, Noise error, Overrun error) @@ -72,6 +80,8 @@ impl<'d, T: BasicInstance, RxDma: super::RxDma> RingBufferedUartRx<'d, T, RxD /// Stop uart background receive fn teardown_uart(&mut self) { + self.ring_buf.request_stop(); + let r = T::regs(); // clear all interrupts and DMA Rx Request // SAFETY: only clears Rx related flags @@ -93,9 +103,6 @@ impl<'d, T: BasicInstance, RxDma: super::RxDma> RingBufferedUartRx<'d, T, RxD } compiler_fence(Ordering::SeqCst); - - self.ring_buf.request_stop(); - while self.ring_buf.is_running() {} } /// Read bytes that are readily available in the ring buffer. @@ -111,96 +118,49 @@ impl<'d, T: BasicInstance, RxDma: super::RxDma> RingBufferedUartRx<'d, T, RxD // Start background receive if it was not already started // SAFETY: read only - let is_started = unsafe { r.cr3().read().dmar() }; - if !is_started { - self.start()?; - } + match unsafe { r.cr3().read().dmar() } { + false => self.start()?, + _ => {} + }; - // SAFETY: read only and we only use Rx related flags - let s = unsafe { sr(r).read() }; - let has_errors = s.pe() || s.fe() || s.ne() || s.ore(); - if has_errors { - self.teardown_uart(); - - if s.pe() { - return Err(Error::Parity); - } else if s.fe() { - return Err(Error::Framing); - } else if s.ne() { - return Err(Error::Noise); - } else { - return Err(Error::Overrun); - } - } - - self.ring_buf.reload_position(); - match self.ring_buf.read(buf) { - Ok(len) if len == 0 => {} - Ok(len) => { - assert!(len > 0); - return Ok(len); - } - Err(OverrunError) => { - // Stop any transfer from now on - // The user must re-start to receive any more data - self.teardown_uart(); - return Err(Error::Overrun); - } - } + check_for_errors(clear_idle_flag(T::regs()))?; loop { - self.wait_for_data_or_idle().await?; + match self.ring_buf.read(buf) { + Ok((0, _)) => {} + Ok((len, _)) => { + return Ok(len); + } + Err(_) => { + return self.stop(Error::Overrun); + } + } - self.ring_buf.reload_position(); - if !self.ring_buf.is_empty() { - break; + match self.wait_for_data_or_idle().await { + Ok(_) => {} + Err(err) => { + return self.stop(err); + } } } - - let len = self.ring_buf.read(buf).map_err(|_err| Error::Overrun)?; - assert!(len > 0); - - Ok(len) } /// Wait for uart idle or dma half-full or full async fn wait_for_data_or_idle(&mut self) -> Result<(), Error> { - let r = T::regs(); - - // make sure USART state is restored to neutral state - let _on_drop = OnDrop::new(move || { - // SAFETY: only clears Rx related flags - unsafe { - r.cr1().modify(|w| { - // disable idle line interrupt - w.set_idleie(false); - }); - } - }); - - // SAFETY: only sets Rx related flags - unsafe { - r.cr1().modify(|w| { - // enable idle line interrupt - w.set_idleie(true); - }); - } - compiler_fence(Ordering::SeqCst); + let mut dma_init = false; // Future which completes when there is dma is half full or full let dma = poll_fn(|cx| { self.ring_buf.set_waker(cx.waker()); - compiler_fence(Ordering::SeqCst); + let status = match dma_init { + false => Poll::Pending, + true => Poll::Ready(()), + }; - self.ring_buf.reload_position(); - if !self.ring_buf.is_empty() { - // Some data is now available - Poll::Ready(()) - } else { - Poll::Pending - } + dma_init = true; + status }); // Future which completes when idle line is detected @@ -210,28 +170,11 @@ impl<'d, T: BasicInstance, RxDma: super::RxDma> RingBufferedUartRx<'d, T, RxD compiler_fence(Ordering::SeqCst); - // SAFETY: read only and we only use Rx related flags - let sr = unsafe { sr(r).read() }; + // Critical section is needed so that IDLE isn't set after + // our read but before we clear it. + let sr = critical_section::with(|_| clear_idle_flag(T::regs())); - // SAFETY: only clears Rx related flags - unsafe { - // This read also clears the error and idle interrupt flags on v1. - rdr(r).read_volatile(); - clear_interrupt_flags(r, sr); - } - - let has_errors = sr.pe() || sr.fe() || sr.ne() || sr.ore(); - if has_errors { - if sr.pe() { - return Poll::Ready(Err(Error::Parity)); - } else if sr.fe() { - return Poll::Ready(Err(Error::Framing)); - } else if sr.ne() { - return Poll::Ready(Err(Error::Noise)); - } else { - return Poll::Ready(Err(Error::Overrun)); - } - } + check_for_errors(sr)?; if sr.idle() { // Idle line is detected @@ -243,11 +186,7 @@ impl<'d, T: BasicInstance, RxDma: super::RxDma> RingBufferedUartRx<'d, T, RxD match select(dma, uart).await { Either::Left(((), _)) => Ok(()), - Either::Right((Ok(()), _)) => Ok(()), - Either::Right((Err(e), _)) => { - self.teardown_uart(); - Err(e) - } + Either::Right((result, _)) => result, } } } @@ -257,6 +196,37 @@ impl> Drop for RingBufferedUartRx<'_, T self.teardown_uart(); } } +/// Return an error result if the Sr register has errors +fn check_for_errors(s: Sr) -> Result<(), Error> { + if s.pe() { + Err(Error::Parity) + } else if s.fe() { + Err(Error::Framing) + } else if s.ne() { + Err(Error::Noise) + } else if s.ore() { + Err(Error::Overrun) + } else { + Ok(()) + } +} + +/// Clear IDLE and return the Sr register +fn clear_idle_flag(r: Regs) -> Sr { + unsafe { + // SAFETY: read only and we only use Rx related flags + + let sr = sr(r).read(); + + // This read also clears the error and idle interrupt flags on v1. + rdr(r).read_volatile(); + clear_interrupt_flags(r, sr); + + r.cr1().modify(|w| w.set_idleie(true)); + + sr + } +} #[cfg(all(feature = "unstable-traits", feature = "nightly"))] mod eio { From 94046f30ffefc96a7b110ed1d9bb60d64cb4780f Mon Sep 17 00:00:00 2001 From: Rasmus Melchior Jacobsen Date: Sat, 27 May 2023 19:26:45 +0200 Subject: [PATCH 154/178] Remove the usage of the local Partition type in BootLoader --- embassy-boot/boot/Cargo.toml | 1 + embassy-boot/boot/src/boot_loader.rs | 344 +++++++++------------------ 2 files changed, 114 insertions(+), 231 deletions(-) diff --git a/embassy-boot/boot/Cargo.toml b/embassy-boot/boot/Cargo.toml index f641d5e1..3fdf69c5 100644 --- a/embassy-boot/boot/Cargo.toml +++ b/embassy-boot/boot/Cargo.toml @@ -39,6 +39,7 @@ env_logger = "0.9" rand = "0.7" # ed25519-dalek v1.0.1 depends on this exact version futures = { version = "0.3", features = ["executor"] } sha1 = "0.10.5" +embassy-embedded-hal = { version = "0.1.0", path = "../../embassy-embedded-hal" } [dev-dependencies.ed25519-dalek] default_features = false diff --git a/embassy-boot/boot/src/boot_loader.rs b/embassy-boot/boot/src/boot_loader.rs index b959de2c..50eb5e66 100644 --- a/embassy-boot/boot/src/boot_loader.rs +++ b/embassy-boot/boot/src/boot_loader.rs @@ -1,6 +1,8 @@ use embedded_storage::nor_flash::{ErrorType, NorFlash, NorFlashError, NorFlashErrorKind, ReadNorFlash}; -use crate::{Partition, State, BOOT_MAGIC, SWAP_MAGIC}; +use crate::{State, BOOT_MAGIC, SWAP_MAGIC}; + +const STATE_ERASE_VALUE: u8 = 0xFF; /// Errors returned by bootloader #[derive(PartialEq, Eq, Debug)] @@ -30,65 +32,39 @@ where } } -/// Trait defining the flash handles used for active and DFU partition. -pub trait FlashConfig { - /// The erase value of the state flash. Typically the default of 0xFF is used, but some flashes use a different value. - const STATE_ERASE_VALUE: u8 = 0xFF; - /// Flash type used for the state partition. - type STATE: NorFlash; - /// Flash type used for the active partition. - type ACTIVE: NorFlash; - /// Flash type used for the dfu partition. - type DFU: NorFlash; - - /// Return flash instance used to write/read to/from active partition. - fn active(&mut self) -> &mut Self::ACTIVE; - /// Return flash instance used to write/read to/from dfu partition. - fn dfu(&mut self) -> &mut Self::DFU; - /// Return flash instance used to write/read to/from bootloader state. - fn state(&mut self) -> &mut Self::STATE; -} - -trait FlashConfigEx { - fn page_size() -> u32; -} - -impl FlashConfigEx for T { - /// Get the page size which is the "unit of operation" within the bootloader. - fn page_size() -> u32 { - core::cmp::max(T::ACTIVE::ERASE_SIZE, T::DFU::ERASE_SIZE) as u32 - } -} - /// BootLoader works with any flash implementing embedded_storage. -pub struct BootLoader { - // Page with current state of bootloader. The state partition has the following format: - // All ranges are in multiples of WRITE_SIZE bytes. - // | Range | Description | - // | 0..1 | Magic indicating bootloader state. BOOT_MAGIC means boot, SWAP_MAGIC means swap. | - // | 1..2 | Progress validity. ERASE_VALUE means valid, !ERASE_VALUE means invalid. | - // | 2..2 + N | Progress index used while swapping or reverting | - state: Partition, - // Location of the partition which will be booted from - active: Partition, - // Location of the partition which will be swapped in when requested - dfu: Partition, +pub struct BootLoader { + /// Flash type used for the active partition - the partition which will be booted from. + active: ACTIVE, + /// Flash type used for the dfu partition - he partition which will be swapped in when requested. + dfu: DFU, + /// Flash type used for the state partition. + /// + /// The state partition has the following format: + /// All ranges are in multiples of WRITE_SIZE bytes. + /// | Range | Description | + /// | 0..1 | Magic indicating bootloader state. BOOT_MAGIC means boot, SWAP_MAGIC means swap. | + /// | 1..2 | Progress validity. ERASE_VALUE means valid, !ERASE_VALUE means invalid. | + /// | 2..2 + N | Progress index used while swapping or reverting + state: STATE, } -impl BootLoader { - /// Create a new instance of a bootloader with the given partitions. +impl BootLoader { + /// Get the page size which is the "unit of operation" within the bootloader. + const PAGE_SIZE: u32 = if ACTIVE::ERASE_SIZE > DFU::ERASE_SIZE { + ACTIVE::ERASE_SIZE as u32 + } else { + DFU::ERASE_SIZE as u32 + }; + + /// Create a new instance of a bootloader with the flash partitions. /// /// - All partitions must be aligned with the PAGE_SIZE const generic parameter. /// - The dfu partition must be at least PAGE_SIZE bigger than the active partition. - pub fn new(active: Partition, dfu: Partition, state: Partition) -> Self { + pub fn new(active: ACTIVE, dfu: DFU, state: STATE) -> Self { Self { active, dfu, state } } - /// Return the offset of the active partition into the active flash. - pub fn boot_address(&self) -> usize { - self.active.from as usize - } - /// Perform necessary boot preparations like swapping images. /// /// The DFU partition is assumed to be 1 page bigger than the active partition for the swap @@ -175,195 +151,174 @@ impl BootLoader { /// | DFU | 3 | 3 | 2 | 1 | 3 | /// +-----------+--------------+--------+--------+--------+--------+ /// - pub fn prepare_boot(&mut self, p: &mut P, aligned_buf: &mut [u8]) -> Result { + pub fn prepare_boot(&mut self, aligned_buf: &mut [u8]) -> Result { // Ensure we have enough progress pages to store copy progress - assert_eq!(0, P::page_size() % aligned_buf.len() as u32); - assert_eq!(0, P::page_size() % P::ACTIVE::WRITE_SIZE as u32); - assert_eq!(0, P::page_size() % P::ACTIVE::ERASE_SIZE as u32); - assert_eq!(0, P::page_size() % P::DFU::WRITE_SIZE as u32); - assert_eq!(0, P::page_size() % P::DFU::ERASE_SIZE as u32); - assert!(aligned_buf.len() >= P::STATE::WRITE_SIZE); - assert_eq!(0, aligned_buf.len() % P::ACTIVE::WRITE_SIZE); - assert_eq!(0, aligned_buf.len() % P::DFU::WRITE_SIZE); - assert_partitions(self.active, self.dfu, self.state, P::page_size(), P::STATE::WRITE_SIZE); + assert_eq!(0, Self::PAGE_SIZE % aligned_buf.len() as u32); + assert_eq!(0, Self::PAGE_SIZE % ACTIVE::WRITE_SIZE as u32); + assert_eq!(0, Self::PAGE_SIZE % ACTIVE::ERASE_SIZE as u32); + assert_eq!(0, Self::PAGE_SIZE % DFU::WRITE_SIZE as u32); + assert_eq!(0, Self::PAGE_SIZE % DFU::ERASE_SIZE as u32); + assert!(aligned_buf.len() >= STATE::WRITE_SIZE); + assert_eq!(0, aligned_buf.len() % ACTIVE::WRITE_SIZE); + assert_eq!(0, aligned_buf.len() % DFU::WRITE_SIZE); + + assert_partitions(&self.active, &self.dfu, &self.state, Self::PAGE_SIZE); // Copy contents from partition N to active - let state = self.read_state(p, aligned_buf)?; + let state = self.read_state(aligned_buf)?; if state == State::Swap { // // Check if we already swapped. If we're in the swap state, this means we should revert // since the app has failed to mark boot as successful // - if !self.is_swapped(p, aligned_buf)? { + if !self.is_swapped(aligned_buf)? { trace!("Swapping"); - self.swap(p, aligned_buf)?; + self.swap(aligned_buf)?; trace!("Swapping done"); } else { trace!("Reverting"); - self.revert(p, aligned_buf)?; + self.revert(aligned_buf)?; - let state_flash = p.state(); - let state_word = &mut aligned_buf[..P::STATE::WRITE_SIZE]; + let state_word = &mut aligned_buf[..STATE::WRITE_SIZE]; // Invalidate progress - state_word.fill(!P::STATE_ERASE_VALUE); - self.state - .write_blocking(state_flash, P::STATE::WRITE_SIZE as u32, state_word)?; + state_word.fill(!STATE_ERASE_VALUE); + self.state.write(STATE::WRITE_SIZE as u32, state_word)?; // Clear magic and progress - self.state.wipe_blocking(state_flash)?; + self.state.erase(0, self.state.capacity() as u32)?; // Set magic state_word.fill(BOOT_MAGIC); - self.state.write_blocking(state_flash, 0, state_word)?; + self.state.write(0, state_word)?; } } Ok(state) } - fn is_swapped(&mut self, p: &mut P, aligned_buf: &mut [u8]) -> Result { - let page_count = (self.active.size() / P::page_size()) as usize; - let progress = self.current_progress(p, aligned_buf)?; + fn is_swapped(&mut self, aligned_buf: &mut [u8]) -> Result { + let page_count = self.active.capacity() / Self::PAGE_SIZE as usize; + let progress = self.current_progress(aligned_buf)?; Ok(progress >= page_count * 2) } - fn current_progress(&mut self, config: &mut P, aligned_buf: &mut [u8]) -> Result { - let write_size = P::STATE::WRITE_SIZE as u32; - let max_index = (((self.state.size() - write_size) / write_size) - 2) as usize; - let state_flash = config.state(); + fn current_progress(&mut self, aligned_buf: &mut [u8]) -> Result { + let write_size = STATE::WRITE_SIZE as u32; + let max_index = ((self.state.capacity() - STATE::WRITE_SIZE) / STATE::WRITE_SIZE) - 2; let state_word = &mut aligned_buf[..write_size as usize]; - self.state.read_blocking(state_flash, write_size, state_word)?; - if state_word.iter().any(|&b| b != P::STATE_ERASE_VALUE) { + self.state.read(write_size, state_word)?; + if state_word.iter().any(|&b| b != STATE_ERASE_VALUE) { // Progress is invalid return Ok(max_index); } for index in 0..max_index { - self.state - .read_blocking(state_flash, (2 + index) as u32 * write_size, state_word)?; + self.state.read((2 + index) as u32 * write_size, state_word)?; - if state_word.iter().any(|&b| b == P::STATE_ERASE_VALUE) { + if state_word.iter().any(|&b| b == STATE_ERASE_VALUE) { return Ok(index); } } Ok(max_index) } - fn update_progress( - &mut self, - progress_index: usize, - p: &mut P, - aligned_buf: &mut [u8], - ) -> Result<(), BootError> { - let state_word = &mut aligned_buf[..P::STATE::WRITE_SIZE]; - state_word.fill(!P::STATE_ERASE_VALUE); - self.state.write_blocking( - p.state(), - (2 + progress_index) as u32 * P::STATE::WRITE_SIZE as u32, - state_word, - )?; + fn update_progress(&mut self, progress_index: usize, aligned_buf: &mut [u8]) -> Result<(), BootError> { + let state_word = &mut aligned_buf[..STATE::WRITE_SIZE]; + state_word.fill(!STATE_ERASE_VALUE); + self.state + .write((2 + progress_index) as u32 * STATE::WRITE_SIZE as u32, state_word)?; Ok(()) } - fn copy_page_once_to_active( + fn copy_page_once_to_active( &mut self, progress_index: usize, from_offset: u32, to_offset: u32, - p: &mut P, aligned_buf: &mut [u8], ) -> Result<(), BootError> { - if self.current_progress(p, aligned_buf)? <= progress_index { - let page_size = P::page_size() as u32; + if self.current_progress(aligned_buf)? <= progress_index { + let page_size = Self::PAGE_SIZE as u32; - self.active - .erase_blocking(p.active(), to_offset, to_offset + page_size)?; + self.active.erase(to_offset, to_offset + page_size)?; for offset_in_page in (0..page_size).step_by(aligned_buf.len()) { - self.dfu - .read_blocking(p.dfu(), from_offset + offset_in_page as u32, aligned_buf)?; - self.active - .write_blocking(p.active(), to_offset + offset_in_page as u32, aligned_buf)?; + self.dfu.read(from_offset + offset_in_page as u32, aligned_buf)?; + self.active.write(to_offset + offset_in_page as u32, aligned_buf)?; } - self.update_progress(progress_index, p, aligned_buf)?; + self.update_progress(progress_index, aligned_buf)?; } Ok(()) } - fn copy_page_once_to_dfu( + fn copy_page_once_to_dfu( &mut self, progress_index: usize, from_offset: u32, to_offset: u32, - p: &mut P, aligned_buf: &mut [u8], ) -> Result<(), BootError> { - if self.current_progress(p, aligned_buf)? <= progress_index { - let page_size = P::page_size() as u32; + if self.current_progress(aligned_buf)? <= progress_index { + let page_size = Self::PAGE_SIZE as u32; - self.dfu - .erase_blocking(p.dfu(), to_offset as u32, to_offset + page_size)?; + self.dfu.erase(to_offset as u32, to_offset + page_size)?; for offset_in_page in (0..page_size).step_by(aligned_buf.len()) { - self.active - .read_blocking(p.active(), from_offset + offset_in_page as u32, aligned_buf)?; - self.dfu - .write_blocking(p.dfu(), to_offset + offset_in_page as u32, aligned_buf)?; + self.active.read(from_offset + offset_in_page as u32, aligned_buf)?; + self.dfu.write(to_offset + offset_in_page as u32, aligned_buf)?; } - self.update_progress(progress_index, p, aligned_buf)?; + self.update_progress(progress_index, aligned_buf)?; } Ok(()) } - fn swap(&mut self, p: &mut P, aligned_buf: &mut [u8]) -> Result<(), BootError> { - let page_size = P::page_size(); - let page_count = self.active.size() / page_size; + fn swap(&mut self, aligned_buf: &mut [u8]) -> Result<(), BootError> { + let page_count = self.active.capacity() as u32 / Self::PAGE_SIZE; for page_num in 0..page_count { let progress_index = (page_num * 2) as usize; // Copy active page to the 'next' DFU page. - let active_from_offset = (page_count - 1 - page_num) * page_size; - let dfu_to_offset = (page_count - page_num) * page_size; + let active_from_offset = (page_count - 1 - page_num) * Self::PAGE_SIZE; + let dfu_to_offset = (page_count - page_num) * Self::PAGE_SIZE; //trace!("Copy active {} to dfu {}", active_from_offset, dfu_to_offset); - self.copy_page_once_to_dfu(progress_index, active_from_offset, dfu_to_offset, p, aligned_buf)?; + self.copy_page_once_to_dfu(progress_index, active_from_offset, dfu_to_offset, aligned_buf)?; // Copy DFU page to the active page - let active_to_offset = (page_count - 1 - page_num) * page_size; - let dfu_from_offset = (page_count - 1 - page_num) * page_size; + let active_to_offset = (page_count - 1 - page_num) * Self::PAGE_SIZE; + let dfu_from_offset = (page_count - 1 - page_num) * Self::PAGE_SIZE; //trace!("Copy dfy {} to active {}", dfu_from_offset, active_to_offset); - self.copy_page_once_to_active(progress_index + 1, dfu_from_offset, active_to_offset, p, aligned_buf)?; + self.copy_page_once_to_active(progress_index + 1, dfu_from_offset, active_to_offset, aligned_buf)?; } Ok(()) } - fn revert(&mut self, p: &mut P, aligned_buf: &mut [u8]) -> Result<(), BootError> { - let page_size = P::page_size(); - let page_count = self.active.size() / page_size; + fn revert(&mut self, aligned_buf: &mut [u8]) -> Result<(), BootError> { + let page_count = self.active.capacity() as u32 / Self::PAGE_SIZE; for page_num in 0..page_count { let progress_index = (page_count * 2 + page_num * 2) as usize; // Copy the bad active page to the DFU page - let active_from_offset = page_num * page_size; - let dfu_to_offset = page_num * page_size; - self.copy_page_once_to_dfu(progress_index, active_from_offset, dfu_to_offset, p, aligned_buf)?; + let active_from_offset = page_num * Self::PAGE_SIZE; + let dfu_to_offset = page_num * Self::PAGE_SIZE; + self.copy_page_once_to_dfu(progress_index, active_from_offset, dfu_to_offset, aligned_buf)?; // Copy the DFU page back to the active page - let active_to_offset = page_num * page_size; - let dfu_from_offset = (page_num + 1) * page_size; - self.copy_page_once_to_active(progress_index + 1, dfu_from_offset, active_to_offset, p, aligned_buf)?; + let active_to_offset = page_num * Self::PAGE_SIZE; + let dfu_from_offset = (page_num + 1) * Self::PAGE_SIZE; + self.copy_page_once_to_active(progress_index + 1, dfu_from_offset, active_to_offset, aligned_buf)?; } Ok(()) } - fn read_state(&mut self, config: &mut P, aligned_buf: &mut [u8]) -> Result { - let state_word = &mut aligned_buf[..P::STATE::WRITE_SIZE]; - self.state.read_blocking(config.state(), 0, state_word)?; + fn read_state(&mut self, aligned_buf: &mut [u8]) -> Result { + let state_word = &mut aligned_buf[..STATE::WRITE_SIZE]; + self.state.read(0, state_word)?; if !state_word.iter().any(|&b| b != SWAP_MAGIC) { Ok(State::Swap) @@ -373,11 +328,16 @@ impl BootLoader { } } -fn assert_partitions(active: Partition, dfu: Partition, state: Partition, page_size: u32, state_write_size: usize) { - assert_eq!(active.size() % page_size, 0); - assert_eq!(dfu.size() % page_size, 0); - assert!(dfu.size() - active.size() >= page_size); - assert!(2 + 2 * (active.size() / page_size) <= state.size() / state_write_size as u32); +fn assert_partitions( + active: &ACTIVE, + dfu: &DFU, + state: &STATE, + page_size: u32, +) { + assert_eq!(active.capacity() as u32 % page_size, 0); + assert_eq!(dfu.capacity() as u32 % page_size, 0); + assert!(dfu.capacity() as u32 - active.capacity() as u32 >= page_size); + assert!(2 + 2 * (active.capacity() as u32 / page_size) <= state.capacity() as u32 / STATE::WRITE_SIZE as u32); } /// A flash wrapper implementing the Flash and embedded_storage traits. @@ -436,98 +396,20 @@ where } } -/// Convenience provider that uses a single flash for all partitions. -pub struct SingleFlashConfig<'a, F> -where - F: NorFlash, -{ - flash: &'a mut F, -} - -impl<'a, F> SingleFlashConfig<'a, F> -where - F: NorFlash, -{ - /// Create a provider for a single flash. - pub fn new(flash: &'a mut F) -> Self { - Self { flash } - } -} - -impl<'a, F> FlashConfig for SingleFlashConfig<'a, F> -where - F: NorFlash, -{ - type STATE = F; - type ACTIVE = F; - type DFU = F; - - fn active(&mut self) -> &mut Self::STATE { - self.flash - } - fn dfu(&mut self) -> &mut Self::ACTIVE { - self.flash - } - fn state(&mut self) -> &mut Self::DFU { - self.flash - } -} - -/// Convenience flash provider that uses separate flash instances for each partition. -pub struct MultiFlashConfig<'a, ACTIVE, STATE, DFU> -where - ACTIVE: NorFlash, - STATE: NorFlash, - DFU: NorFlash, -{ - active: &'a mut ACTIVE, - state: &'a mut STATE, - dfu: &'a mut DFU, -} - -impl<'a, ACTIVE, STATE, DFU> MultiFlashConfig<'a, ACTIVE, STATE, DFU> -where - ACTIVE: NorFlash, - STATE: NorFlash, - DFU: NorFlash, -{ - /// Create a new flash provider with separate configuration for all three partitions. - pub fn new(active: &'a mut ACTIVE, state: &'a mut STATE, dfu: &'a mut DFU) -> Self { - Self { active, state, dfu } - } -} - -impl<'a, ACTIVE, STATE, DFU> FlashConfig for MultiFlashConfig<'a, ACTIVE, STATE, DFU> -where - ACTIVE: NorFlash, - STATE: NorFlash, - DFU: NorFlash, -{ - type STATE = STATE; - type ACTIVE = ACTIVE; - type DFU = DFU; - - fn active(&mut self) -> &mut Self::ACTIVE { - self.active - } - fn dfu(&mut self) -> &mut Self::DFU { - self.dfu - } - fn state(&mut self) -> &mut Self::STATE { - self.state - } -} - #[cfg(test)] mod tests { use super::*; + use crate::mem_flash::MemFlash; #[test] #[should_panic] fn test_range_asserts() { - const ACTIVE: Partition = Partition::new(4096, 4194304); - const DFU: Partition = Partition::new(4194304, 2 * 4194304); - const STATE: Partition = Partition::new(0, 4096); - assert_partitions(ACTIVE, DFU, STATE, 4096, 4); + const ACTIVE_SIZE: usize = 4194304 - 4096; + const DFU_SIZE: usize = 4194304; + const STATE_SIZE: usize = 4096; + static ACTIVE: MemFlash = MemFlash::new(0xFF); + static DFU: MemFlash = MemFlash::new(0xFF); + static STATE: MemFlash = MemFlash::new(0xFF); + assert_partitions(&ACTIVE, &DFU, &STATE, 4096); } } From 1a31b03976d73496f649165d9f92c4e19bef93fc Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Mon, 29 May 2023 21:30:28 +0200 Subject: [PATCH 155/178] ci: fix nrf, rp tests. --- .github/ci/test.sh | 4 ++- embassy-boot/nrf/Cargo.toml | 2 +- embassy-nrf/Cargo.toml | 33 ++++++++++++++++------- embassy-nrf/README.md | 2 +- embassy-nrf/src/qdec.rs | 13 +++++++-- embassy-nrf/src/temp.rs | 13 ++++++++- embassy-rp/Cargo.toml | 8 +++++- embassy-rp/src/flash.rs | 1 + embassy-rp/src/intrinsics.rs | 19 ++++++------- embassy-rp/src/multicore.rs | 25 ++++++++++++++--- embassy-rp/src/rtc/mod.rs | 2 +- examples/boot/bootloader/nrf/Cargo.toml | 4 +-- examples/boot/bootloader/rp/Cargo.toml | 4 +-- examples/boot/bootloader/stm32/Cargo.toml | 4 +-- 14 files changed, 98 insertions(+), 36 deletions(-) diff --git a/.github/ci/test.sh b/.github/ci/test.sh index a7140cfd..d014e4bd 100755 --- a/.github/ci/test.sh +++ b/.github/ci/test.sh @@ -21,7 +21,9 @@ cargo test --manifest-path ./embassy-boot/boot/Cargo.toml --features nightly cargo test --manifest-path ./embassy-boot/boot/Cargo.toml --features nightly,ed25519-dalek cargo test --manifest-path ./embassy-boot/boot/Cargo.toml --features nightly,ed25519-salty -#cargo test --manifest-path ./embassy-nrf/Cargo.toml --no-default-features --features nightly,nrf52840,time-driver-rtc1 ## broken doctests +cargo test --manifest-path ./embassy-nrf/Cargo.toml --no-default-features --features nightly,nrf52840,time-driver-rtc1,gpiote + +cargo test --manifest-path ./embassy-rp/Cargo.toml --no-default-features --features nightly,time-driver cargo test --manifest-path ./embassy-stm32/Cargo.toml --no-default-features --features nightly,stm32f429vg,exti,time-driver-any,exti cargo test --manifest-path ./embassy-stm32/Cargo.toml --no-default-features --features nightly,stm32f732ze,exti,time-driver-any,exti diff --git a/embassy-boot/nrf/Cargo.toml b/embassy-boot/nrf/Cargo.toml index e4673688..8186a995 100644 --- a/embassy-boot/nrf/Cargo.toml +++ b/embassy-boot/nrf/Cargo.toml @@ -17,7 +17,7 @@ target = "thumbv7em-none-eabi" defmt = { version = "0.3", optional = true } embassy-sync = { path = "../../embassy-sync" } -embassy-nrf = { path = "../../embassy-nrf", default-features = false } +embassy-nrf = { path = "../../embassy-nrf" } embassy-boot = { path = "../boot", default-features = false } cortex-m = { version = "0.7.6" } cortex-m-rt = { version = "0.7" } diff --git a/embassy-nrf/Cargo.toml b/embassy-nrf/Cargo.toml index 15d0f687..83900d4d 100644 --- a/embassy-nrf/Cargo.toml +++ b/embassy-nrf/Cargo.toml @@ -16,6 +16,18 @@ flavors = [ ] [features] +default = [ + "nrf52805-pac?/rt", + "nrf52810-pac?/rt", + "nrf52811-pac?/rt", + "nrf52820-pac?/rt", + "nrf52832-pac?/rt", + "nrf52833-pac?/rt", + "nrf52840-pac?/rt", + "nrf5340-app-pac?/rt", + "nrf5340-net-pac?/rt", + "nrf9160-pac?/rt", +] time = ["dep:embassy-time"] @@ -103,13 +115,14 @@ embedded-storage = "0.3.0" embedded-storage-async = { version = "0.4.0", optional = true } cfg-if = "1.0.0" -nrf52805-pac = { version = "0.12.0", optional = true, features = [ "rt" ] } -nrf52810-pac = { version = "0.12.0", optional = true, features = [ "rt" ] } -nrf52811-pac = { version = "0.12.0", optional = true, features = [ "rt" ] } -nrf52820-pac = { version = "0.12.0", optional = true, features = [ "rt" ] } -nrf52832-pac = { version = "0.12.0", optional = true, features = [ "rt" ] } -nrf52833-pac = { version = "0.12.0", optional = true, features = [ "rt" ] } -nrf52840-pac = { version = "0.12.0", optional = true, features = [ "rt" ] } -nrf5340-app-pac = { version = "0.12.0", optional = true, features = [ "rt" ] } -nrf5340-net-pac = { version = "0.12.0", optional = true, features = [ "rt" ] } -nrf9160-pac = { version = "0.12.0", optional = true, features = [ "rt" ] } +nrf52805-pac = { version = "0.12.0", optional = true } +nrf52810-pac = { version = "0.12.0", optional = true } +nrf52811-pac = { version = "0.12.0", optional = true } +nrf52820-pac = { version = "0.12.0", optional = true } +nrf52832-pac = { version = "0.12.0", optional = true } +nrf52833-pac = { version = "0.12.0", optional = true } +nrf52840-pac = { version = "0.12.0", optional = true } +nrf5340-app-pac = { version = "0.12.0", optional = true } +nrf5340-net-pac = { version = "0.12.0", optional = true } +nrf9160-pac = { version = "0.12.0", optional = true } + diff --git a/embassy-nrf/README.md b/embassy-nrf/README.md index a31cfae6..129ec0c0 100644 --- a/embassy-nrf/README.md +++ b/embassy-nrf/README.md @@ -13,7 +13,7 @@ with peripherals. It takes care of sending/receiving data over a variety of bus However, EasyDMA requires the buffers used to transmit and receive data to reside in RAM. Unfortunately, Rust slices will not always do so. The following example using the SPI peripheral shows a common situation where this might happen: -```no_run +```rust,ignore // As we pass a slice to the function whose contents will not ever change, // the compiler writes it into the flash and thus the pointer to it will // reference static memory. Since EasyDMA requires slices to reside in RAM, diff --git a/embassy-nrf/src/qdec.rs b/embassy-nrf/src/qdec.rs index 4d2a0919..c845492f 100644 --- a/embassy-nrf/src/qdec.rs +++ b/embassy-nrf/src/qdec.rs @@ -154,10 +154,19 @@ impl<'d, T: Instance> Qdec<'d, T> { /// # Example /// /// ```no_run - /// let irq = interrupt::take!(QDEC); + /// use embassy_nrf::qdec::{self, Qdec}; + /// use embassy_nrf::{bind_interrupts, peripherals}; + /// + /// bind_interrupts!(struct Irqs { + /// QDEC => qdec::InterruptHandler; + /// }); + /// + /// # async { + /// # let p: embassy_nrf::Peripherals = todo!(); /// let config = qdec::Config::default(); - /// let mut q = Qdec::new(p.QDEC, p.P0_31, p.P0_30, config); + /// let mut q = Qdec::new(p.QDEC, Irqs, p.P0_31, p.P0_30, config); /// let delta = q.read().await; + /// # }; /// ``` pub async fn read(&mut self) -> i16 { let t = T::regs(); diff --git a/embassy-nrf/src/temp.rs b/embassy-nrf/src/temp.rs index 3a75ec62..0653710a 100644 --- a/embassy-nrf/src/temp.rs +++ b/embassy-nrf/src/temp.rs @@ -56,8 +56,19 @@ impl<'d> Temp<'d> { /// # Example /// /// ```no_run - /// let mut t = Temp::new(p.TEMP, interrupt::take!(TEMP)); + /// use embassy_nrf::{bind_interrupts, temp}; + /// use embassy_nrf::temp::Temp; + /// use embassy_time::{Duration, Timer}; + /// + /// bind_interrupts!(struct Irqs { + /// TEMP => temp::InterruptHandler; + /// }); + /// + /// # async { + /// # let p: embassy_nrf::Peripherals = todo!(); + /// let mut t = Temp::new(p.TEMP, Irqs); /// let v: u16 = t.read().await.to_num::(); + /// # }; /// ``` pub async fn read(&mut self) -> I30F2 { // In case the future is dropped, stop the task and reset events. diff --git a/embassy-rp/Cargo.toml b/embassy-rp/Cargo.toml index e395a994..e032dfda 100644 --- a/embassy-rp/Cargo.toml +++ b/embassy-rp/Cargo.toml @@ -13,6 +13,8 @@ flavors = [ ] [features] +default = [ "rp-pac/rt" ] + defmt = ["dep:defmt", "embassy-usb-driver?/defmt", "embassy-hal-common/defmt"] # critical section that is safe for multicore use @@ -70,7 +72,7 @@ embedded-storage = { version = "0.3" } rand_core = "0.6.4" fixed = "1.23.1" -rp-pac = { version = "4", features = ["rt"] } +rp-pac = { version = "4" } embedded-hal-02 = { package = "embedded-hal", version = "0.2.6", features = ["unproven"] } embedded-hal-1 = { package = "embedded-hal", version = "=1.0.0-alpha.10", optional = true} @@ -81,3 +83,7 @@ paste = "1.0" pio-proc = {version= "0.2" } pio = {version= "0.2.1" } rp2040-boot2 = "0.3" + +[dev-dependencies] +embassy-executor = { version = "0.2.0", path = "../embassy-executor", features = ["arch-std", "executor-thread"] } +static_cell = "1.0" diff --git a/embassy-rp/src/flash.rs b/embassy-rp/src/flash.rs index 929bd028..0410429e 100644 --- a/embassy-rp/src/flash.rs +++ b/embassy-rp/src/flash.rs @@ -575,6 +575,7 @@ mod ram_helpers { #[inline(never)] #[link_section = ".data.ram_func"] unsafe fn read_flash_inner(cmd: FlashCommand, ptrs: *const FlashFunctionPointers) { + #[cfg(target_arch = "arm")] core::arch::asm!( "mov r10, r0", // cmd "mov r5, r1", // ptrs diff --git a/embassy-rp/src/intrinsics.rs b/embassy-rp/src/intrinsics.rs index 3baabb28..5b9c127b 100644 --- a/embassy-rp/src/intrinsics.rs +++ b/embassy-rp/src/intrinsics.rs @@ -61,16 +61,17 @@ macro_rules! intrinsics_aliases { /// Like the compiler-builtins macro, it accepts a series of functions that /// looks like normal Rust code: /// -/// intrinsics! { -/// extern "C" fn foo(a: i32) -> u32 { -/// // ... -/// } -/// -/// #[nonstandard_attribute] -/// extern "C" fn bar(a: i32) -> u32 { -/// // ... -/// } +/// ```rust,ignore +/// intrinsics! { +/// extern "C" fn foo(a: i32) -> u32 { +/// // ... /// } +/// #[nonstandard_attribute] +/// extern "C" fn bar(a: i32) -> u32 { +/// // ... +/// } +/// } +/// ``` /// /// Each function can also be decorated with nonstandard attributes to control /// additional behaviour: diff --git a/embassy-rp/src/multicore.rs b/embassy-rp/src/multicore.rs index bbc77510..a13209f7 100644 --- a/embassy-rp/src/multicore.rs +++ b/embassy-rp/src/multicore.rs @@ -9,22 +9,41 @@ //! the `embassy-sync` primitives and `CriticalSectionRawMutex`. //! //! # Usage +//! //! ```no_run +//! # #![feature(type_alias_impl_trait)] +//! use embassy_rp::multicore::Stack; +//! use static_cell::StaticCell; +//! use embassy_executor::Executor; +//! //! static mut CORE1_STACK: Stack<4096> = Stack::new(); //! static EXECUTOR0: StaticCell = StaticCell::new(); //! static EXECUTOR1: StaticCell = StaticCell::new(); //! +//! # // workaround weird error: `main` function not found in crate `rust_out` +//! # let _ = (); +//! +//! #[embassy_executor::task] +//! async fn core0_task() { +//! // ... +//! } +//! +//! #[embassy_executor::task] +//! async fn core1_task() { +//! // ... +//! } +//! //! #[cortex_m_rt::entry] //! fn main() -> ! { //! let p = embassy_rp::init(Default::default()); //! -//! spawn_core1(p.CORE1, unsafe { &mut CORE1_STACK }, move || { +//! embassy_rp::multicore::spawn_core1(p.CORE1, unsafe { &mut CORE1_STACK }, move || { //! let executor1 = EXECUTOR1.init(Executor::new()); -//! executor1.run(|spawner| unwrap!(spawner.spawn(core1_task()))); +//! executor1.run(|spawner| spawner.spawn(core1_task()).unwrap()); //! }); //! //! let executor0 = EXECUTOR0.init(Executor::new()); -//! executor0.run(|spawner| unwrap!(spawner.spawn(core0_task()))); +//! executor0.run(|spawner| spawner.spawn(core0_task()).unwrap()) //! } //! ``` diff --git a/embassy-rp/src/rtc/mod.rs b/embassy-rp/src/rtc/mod.rs index c213ad17..e1d886d4 100644 --- a/embassy-rp/src/rtc/mod.rs +++ b/embassy-rp/src/rtc/mod.rs @@ -121,7 +121,7 @@ impl<'d, T: Instance> RealTimeClock<'d, T> { /// # #[cfg(not(feature = "chrono"))] /// # fn main() { /// # use embassy_rp::rtc::{RealTimeClock, DateTimeFilter}; - /// # let mut real_time_clock: RealTimeClock = unsafe { core::mem::zeroed() }; + /// # let mut real_time_clock: RealTimeClock = unsafe { core::mem::zeroed() }; /// let now = real_time_clock.now().unwrap(); /// real_time_clock.schedule_alarm( /// DateTimeFilter::default() diff --git a/examples/boot/bootloader/nrf/Cargo.toml b/examples/boot/bootloader/nrf/Cargo.toml index cd0be5b4..8c2fb4c5 100644 --- a/examples/boot/bootloader/nrf/Cargo.toml +++ b/examples/boot/bootloader/nrf/Cargo.toml @@ -9,8 +9,8 @@ license = "MIT OR Apache-2.0" defmt = { version = "0.3", optional = true } defmt-rtt = { version = "0.4", optional = true } -embassy-nrf = { path = "../../../../embassy-nrf", default-features = false, features = ["nightly"] } -embassy-boot-nrf = { path = "../../../../embassy-boot/nrf", default-features = false } +embassy-nrf = { path = "../../../../embassy-nrf", features = ["nightly"] } +embassy-boot-nrf = { path = "../../../../embassy-boot/nrf" } cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } cortex-m-rt = { version = "0.7" } cfg-if = "1.0.0" diff --git a/examples/boot/bootloader/rp/Cargo.toml b/examples/boot/bootloader/rp/Cargo.toml index b4167bcd..bf922699 100644 --- a/examples/boot/bootloader/rp/Cargo.toml +++ b/examples/boot/bootloader/rp/Cargo.toml @@ -9,8 +9,8 @@ license = "MIT OR Apache-2.0" defmt = { version = "0.3", optional = true } defmt-rtt = { version = "0.4", optional = true } -embassy-rp = { path = "../../../../embassy-rp", default-features = false, features = ["nightly"] } -embassy-boot-rp = { path = "../../../../embassy-boot/rp", default-features = false } +embassy-rp = { path = "../../../../embassy-rp", features = ["nightly"] } +embassy-boot-rp = { path = "../../../../embassy-boot/rp" } embassy-time = { path = "../../../../embassy-time", features = ["nightly"] } cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } diff --git a/examples/boot/bootloader/stm32/Cargo.toml b/examples/boot/bootloader/stm32/Cargo.toml index f2675aa7..fbc80b34 100644 --- a/examples/boot/bootloader/stm32/Cargo.toml +++ b/examples/boot/bootloader/stm32/Cargo.toml @@ -9,8 +9,8 @@ license = "MIT OR Apache-2.0" defmt = { version = "0.3", optional = true } defmt-rtt = { version = "0.4", optional = true } -embassy-stm32 = { path = "../../../../embassy-stm32", default-features = false, features = ["nightly"] } -embassy-boot-stm32 = { path = "../../../../embassy-boot/stm32", default-features = false } +embassy-stm32 = { path = "../../../../embassy-stm32", features = ["nightly"] } +embassy-boot-stm32 = { path = "../../../../embassy-boot/stm32" } cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } cortex-m-rt = { version = "0.7" } embedded-storage = "0.3.0" From da0be7114f87053fe8b38cf64e4d42badd8b4786 Mon Sep 17 00:00:00 2001 From: xoviat Date: Mon, 29 May 2023 15:14:43 -0500 Subject: [PATCH 156/178] stm32/uart: fix dma ringbuf tests --- embassy-stm32/src/dma/ringbuffer.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/embassy-stm32/src/dma/ringbuffer.rs b/embassy-stm32/src/dma/ringbuffer.rs index 72a84a57..a2bde986 100644 --- a/embassy-stm32/src/dma/ringbuffer.rs +++ b/embassy-stm32/src/dma/ringbuffer.rs @@ -276,7 +276,7 @@ mod tests { let mut ringbuf = DmaRingBuffer::new(&mut dma_buf); assert_eq!(0, ringbuf.start); - assert_eq!(16, ringbuf.len()); + assert_eq!(16, ringbuf.cap()); dma.setup(vec![ TestCircularTransferRequest::PositionRequest(8), @@ -317,7 +317,7 @@ mod tests { let mut ringbuf = DmaRingBuffer::new(&mut dma_buf); assert_eq!(0, ringbuf.start); - assert_eq!(16, ringbuf.len()); + assert_eq!(16, ringbuf.cap()); /* Read to close to the end of the buffer @@ -352,7 +352,7 @@ mod tests { let mut ringbuf = DmaRingBuffer::new(&mut dma_buf); assert_eq!(0, ringbuf.start); - assert_eq!(16, ringbuf.len()); + assert_eq!(16, ringbuf.cap()); /* Read to close to the end of the buffer @@ -387,7 +387,7 @@ mod tests { let mut ringbuf = DmaRingBuffer::new(&mut dma_buf); assert_eq!(0, ringbuf.start); - assert_eq!(16, ringbuf.len()); + assert_eq!(16, ringbuf.cap()); /* Read to about the middle of the buffer @@ -423,7 +423,7 @@ mod tests { let mut ringbuf = DmaRingBuffer::new(&mut dma_buf); assert_eq!(0, ringbuf.start); - assert_eq!(16, ringbuf.len()); + assert_eq!(16, ringbuf.cap()); /* Read a few bytes @@ -457,7 +457,7 @@ mod tests { let mut ringbuf = DmaRingBuffer::new(&mut dma_buf); assert_eq!(0, ringbuf.start); - assert_eq!(16, ringbuf.len()); + assert_eq!(16, ringbuf.cap()); /* Read to close to the end of the buffer From 42a5b14724ffc9efe3a011fb5a23d77b66af3a1a Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Mon, 29 May 2023 22:50:30 +0200 Subject: [PATCH 157/178] Remove unneeded default-features=false. --- .../ROOT/examples/layer-by-layer/blinky-async/Cargo.toml | 4 ++-- .../ROOT/examples/layer-by-layer/blinky-hal/Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/modules/ROOT/examples/layer-by-layer/blinky-async/Cargo.toml b/docs/modules/ROOT/examples/layer-by-layer/blinky-async/Cargo.toml index 523de0dd..a7236ed5 100644 --- a/docs/modules/ROOT/examples/layer-by-layer/blinky-async/Cargo.toml +++ b/docs/modules/ROOT/examples/layer-by-layer/blinky-async/Cargo.toml @@ -7,8 +7,8 @@ license = "MIT OR Apache-2.0" [dependencies] cortex-m = "0.7" cortex-m-rt = "0.7" -embassy-stm32 = { version = "0.1.0", features = ["stm32l475vg", "memory-x", "exti"], default-features = false } -embassy-executor = { version = "0.2.0", default-features = false, features = ["nightly", "arch-cortex-m", "executor-thread"] } +embassy-stm32 = { version = "0.1.0", features = ["stm32l475vg", "memory-x", "exti"] } +embassy-executor = { version = "0.2.0", features = ["nightly", "arch-cortex-m", "executor-thread"] } defmt = "0.3.0" defmt-rtt = "0.3.0" diff --git a/docs/modules/ROOT/examples/layer-by-layer/blinky-hal/Cargo.toml b/docs/modules/ROOT/examples/layer-by-layer/blinky-hal/Cargo.toml index f86361dd..c15de2db 100644 --- a/docs/modules/ROOT/examples/layer-by-layer/blinky-hal/Cargo.toml +++ b/docs/modules/ROOT/examples/layer-by-layer/blinky-hal/Cargo.toml @@ -7,7 +7,7 @@ license = "MIT OR Apache-2.0" [dependencies] cortex-m = "0.7" cortex-m-rt = "0.7" -embassy-stm32 = { version = "0.1.0", features = ["stm32l475vg", "memory-x"], default-features = false } +embassy-stm32 = { version = "0.1.0", features = ["stm32l475vg", "memory-x"] } defmt = "0.3.0" defmt-rtt = "0.3.0" From 020e956f1ba5c0b3baf75b02f286218f661e1c02 Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Tue, 30 May 2023 00:10:36 +0200 Subject: [PATCH 158/178] ci: run HIL tests in parallel. --- .github/ci/build.sh | 1 + ci.sh | 43 +++++-------------- tests/nrf/.cargo/config.toml | 2 +- tests/nrf/Cargo.toml | 2 + tests/nrf/build.rs | 1 + tests/nrf/src/bin/buffered_uart.rs | 2 + tests/nrf/src/bin/buffered_uart_spam.rs | 2 + tests/nrf/src/bin/timer.rs | 2 + tests/nrf/src/common.rs | 1 + tests/rp/.cargo/config.toml | 4 +- tests/rp/Cargo.toml | 2 + tests/rp/build.rs | 1 + tests/rp/src/bin/dma_copy_async.rs | 2 + tests/rp/src/bin/flash.rs | 2 + tests/rp/src/bin/float.rs | 2 + tests/rp/src/bin/gpio.rs | 2 + tests/rp/src/bin/gpio_async.rs | 2 + tests/rp/src/bin/gpio_multicore.rs | 2 + tests/rp/src/bin/multicore.rs | 2 + tests/rp/src/bin/pwm.rs | 2 + tests/rp/src/bin/spi.rs | 2 + tests/rp/src/bin/spi_async.rs | 2 + tests/rp/src/bin/uart.rs | 2 + tests/rp/src/bin/uart_buffered.rs | 2 + tests/rp/src/bin/uart_dma.rs | 2 + tests/rp/src/bin/uart_upgrade.rs | 2 + tests/rp/src/common.rs | 1 + tests/stm32/.cargo/config.toml | 2 +- tests/stm32/Cargo.toml | 2 + tests/stm32/build.rs | 1 + tests/stm32/src/bin/gpio.rs | 6 +-- tests/stm32/src/bin/rtc.rs | 8 ++-- tests/stm32/src/bin/sdmmc.rs | 2 + tests/stm32/src/bin/spi.rs | 6 +-- tests/stm32/src/bin/spi_dma.rs | 6 +-- tests/stm32/src/bin/timer.rs | 6 +-- tests/stm32/src/bin/tl_mbox.rs | 10 ++--- tests/stm32/src/bin/usart.rs | 6 +-- tests/stm32/src/bin/usart_dma.rs | 6 +-- tests/stm32/src/bin/usart_rx_ringbuffered.rs | 6 +-- tests/stm32/src/common.rs | 44 ++++++++++++++++++++ tests/stm32/src/example_common.rs | 25 ----------- 42 files changed, 136 insertions(+), 92 deletions(-) create mode 100644 tests/nrf/src/common.rs create mode 100644 tests/rp/src/common.rs create mode 100644 tests/stm32/src/common.rs delete mode 100644 tests/stm32/src/example_common.rs diff --git a/.github/ci/build.sh b/.github/ci/build.sh index 1c3a7f3b..30ca1e6f 100755 --- a/.github/ci/build.sh +++ b/.github/ci/build.sh @@ -9,6 +9,7 @@ export CARGO_HOME=/ci/cache/cargo export CARGO_TARGET_DIR=/ci/cache/target if [ -f /ci/secrets/teleprobe-token.txt ]; then echo Got teleprobe token! + export TELEPROBE_HOST=https://teleprobe.embassy.dev export TELEPROBE_TOKEN=$(cat /ci/secrets/teleprobe-token.txt) fi diff --git a/ci.sh b/ci.sh index 11c56932..1eafda3a 100755 --- a/ci.sh +++ b/ci.sh @@ -12,7 +12,7 @@ if [ $TARGET = "x86_64-unknown-linux-gnu" ]; then BUILD_EXTRA="--- build --release --manifest-path examples/std/Cargo.toml --target $TARGET --out-dir out/examples/std" fi -find . -name '*.rs' -not -path '*target*' | xargs rustfmt --check --skip-children --unstable-features --edition 2018 +find . -name '*.rs' -not -path '*target*' | xargs rustfmt --check --skip-children --unstable-features --edition 2021 cargo batch \ --- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi --features nightly \ @@ -127,45 +127,24 @@ cargo batch \ --- build --release --manifest-path examples/boot/bootloader/rp/Cargo.toml --target thumbv6m-none-eabi \ --- build --release --manifest-path examples/boot/bootloader/stm32/Cargo.toml --target thumbv7em-none-eabi --features embassy-stm32/stm32wl55jc-cm4 \ --- build --release --manifest-path examples/wasm/Cargo.toml --target wasm32-unknown-unknown --out-dir out/examples/wasm \ - --- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7m-none-eabi --features stm32f103c8 --out-dir out/tests/bluepill-stm32f103c8 \ - --- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32f429zi --out-dir out/tests/nucleo-stm32f429zi \ - --- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32g491re --out-dir out/tests/nucleo-stm32g491re \ - --- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv6m-none-eabi --features stm32g071rb --out-dir out/tests/nucleo-stm32g071rb \ - --- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv6m-none-eabi --features stm32c031c6 --out-dir out/tests/nucleo-stm32c031c6 \ - --- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32h755zi --out-dir out/tests/nucleo-stm32h755zi \ - --- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32wb55rg --out-dir out/tests/nucleo-stm32wb55rg \ - --- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32h563zi --out-dir out/tests/nucleo-stm32h563zi \ - --- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32u585ai --out-dir out/tests/iot-stm32u585ai \ + --- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7m-none-eabi --features stm32f103c8 --out-dir out/tests/stm32f103c8 \ + --- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32f429zi --out-dir out/tests/stm32f429zi \ + --- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32g491re --out-dir out/tests/stm32g491re \ + --- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv6m-none-eabi --features stm32g071rb --out-dir out/tests/stm32g071rb \ + --- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv6m-none-eabi --features stm32c031c6 --out-dir out/tests/stm32c031c6 \ + --- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32h755zi --out-dir out/tests/stm32h755zi \ + --- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32wb55rg --out-dir out/tests/stm32wb55rg \ + --- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32h563zi --out-dir out/tests/stm32h563zi \ + --- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32u585ai --out-dir out/tests/stm32u585ai \ --- build --release --manifest-path tests/rp/Cargo.toml --target thumbv6m-none-eabi --out-dir out/tests/rpi-pico \ --- build --release --manifest-path tests/nrf/Cargo.toml --target thumbv7em-none-eabi --out-dir out/tests/nrf52840-dk \ --- build --release --manifest-path tests/riscv32/Cargo.toml --target riscv32imac-unknown-none-elf \ $BUILD_EXTRA - -function run_elf { - echo Running target=$1 elf=$2 - STATUSCODE=$( - curl \ - -sS \ - --output /dev/stderr \ - --write-out "%{http_code}" \ - -H "Authorization: Bearer $TELEPROBE_TOKEN" \ - https://teleprobe.embassy.dev/targets/$1/run --data-binary @$2 - ) - echo - echo HTTP Status code: $STATUSCODE - test "$STATUSCODE" -eq 200 -} - if [[ -z "${TELEPROBE_TOKEN-}" ]]; then echo No teleprobe token found, skipping running HIL tests exit fi -for board in $(ls out/tests); do - echo Running tests for board: $board - for elf in $(ls out/tests/$board); do - run_elf $board out/tests/$board/$elf - done -done +teleprobe client run -r out/tests \ No newline at end of file diff --git a/tests/nrf/.cargo/config.toml b/tests/nrf/.cargo/config.toml index 4eec189d..03995f96 100644 --- a/tests/nrf/.cargo/config.toml +++ b/tests/nrf/.cargo/config.toml @@ -1,6 +1,6 @@ [target.'cfg(all(target_arch = "arm", target_os = "none"))'] #runner = "teleprobe local run --chip nRF52840_xxAA --elf" -runner = "teleprobe client run --target nrf52840-dk --elf" +runner = "teleprobe client run" [build] target = "thumbv7em-none-eabi" diff --git a/tests/nrf/Cargo.toml b/tests/nrf/Cargo.toml index ac38229a..9735c87d 100644 --- a/tests/nrf/Cargo.toml +++ b/tests/nrf/Cargo.toml @@ -5,6 +5,8 @@ version = "0.1.0" license = "MIT OR Apache-2.0" [dependencies] +teleprobe-meta = "1" + embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } embassy-sync = { version = "0.2.0", path = "../../embassy-sync", features = ["defmt", "nightly"] } embassy-executor = { version = "0.2.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "defmt", "nightly", "integrated-timers"] } diff --git a/tests/nrf/build.rs b/tests/nrf/build.rs index 6f487224..93e2a28c 100644 --- a/tests/nrf/build.rs +++ b/tests/nrf/build.rs @@ -11,6 +11,7 @@ fn main() -> Result<(), Box> { println!("cargo:rustc-link-arg-bins=--nmagic"); println!("cargo:rustc-link-arg-bins=-Tlink_ram.x"); println!("cargo:rustc-link-arg-bins=-Tdefmt.x"); + println!("cargo:rustc-link-arg-bins=-Tteleprobe.x"); Ok(()) } diff --git a/tests/nrf/src/bin/buffered_uart.rs b/tests/nrf/src/bin/buffered_uart.rs index e73d4f0b..72a4cb4e 100644 --- a/tests/nrf/src/bin/buffered_uart.rs +++ b/tests/nrf/src/bin/buffered_uart.rs @@ -1,6 +1,8 @@ #![no_std] #![no_main] #![feature(type_alias_impl_trait)] +#[path = "../common.rs"] +mod common; use defmt::{assert_eq, *}; use embassy_executor::Spawner; diff --git a/tests/nrf/src/bin/buffered_uart_spam.rs b/tests/nrf/src/bin/buffered_uart_spam.rs index 74eda6d0..50960206 100644 --- a/tests/nrf/src/bin/buffered_uart_spam.rs +++ b/tests/nrf/src/bin/buffered_uart_spam.rs @@ -1,6 +1,8 @@ #![no_std] #![no_main] #![feature(type_alias_impl_trait)] +#[path = "../common.rs"] +mod common; use core::mem; use core::ptr::NonNull; diff --git a/tests/nrf/src/bin/timer.rs b/tests/nrf/src/bin/timer.rs index 9b9b5fb2..607c5bbf 100644 --- a/tests/nrf/src/bin/timer.rs +++ b/tests/nrf/src/bin/timer.rs @@ -1,6 +1,8 @@ #![no_std] #![no_main] #![feature(type_alias_impl_trait)] +#[path = "../common.rs"] +mod common; use defmt::{assert, info}; use embassy_executor::Spawner; diff --git a/tests/nrf/src/common.rs b/tests/nrf/src/common.rs new file mode 100644 index 00000000..1a05ac1c --- /dev/null +++ b/tests/nrf/src/common.rs @@ -0,0 +1 @@ +teleprobe_meta::target!(b"nrf52840-dk"); diff --git a/tests/rp/.cargo/config.toml b/tests/rp/.cargo/config.toml index e1744c70..bc92e788 100644 --- a/tests/rp/.cargo/config.toml +++ b/tests/rp/.cargo/config.toml @@ -5,8 +5,8 @@ #build-std-features = ["panic_immediate_abort"] [target.'cfg(all(target_arch = "arm", target_os = "none"))'] -#runner = "teleprobe client run --target rpi-pico --elf" -runner = "teleprobe local run --chip RP2040 --elf" +runner = "teleprobe client run" +#runner = "teleprobe local run --chip RP2040 --elf" rustflags = [ # Code-size optimizations. diff --git a/tests/rp/Cargo.toml b/tests/rp/Cargo.toml index 43167166..1786baee 100644 --- a/tests/rp/Cargo.toml +++ b/tests/rp/Cargo.toml @@ -5,6 +5,8 @@ version = "0.1.0" license = "MIT OR Apache-2.0" [dependencies] +teleprobe-meta = "1" + embassy-sync = { version = "0.2.0", path = "../../embassy-sync", features = ["defmt"] } embassy-executor = { version = "0.2.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } embassy-time = { version = "0.1.0", path = "../../embassy-time", features = ["defmt"] } diff --git a/tests/rp/build.rs b/tests/rp/build.rs index 6f487224..93e2a28c 100644 --- a/tests/rp/build.rs +++ b/tests/rp/build.rs @@ -11,6 +11,7 @@ fn main() -> Result<(), Box> { println!("cargo:rustc-link-arg-bins=--nmagic"); println!("cargo:rustc-link-arg-bins=-Tlink_ram.x"); println!("cargo:rustc-link-arg-bins=-Tdefmt.x"); + println!("cargo:rustc-link-arg-bins=-Tteleprobe.x"); Ok(()) } diff --git a/tests/rp/src/bin/dma_copy_async.rs b/tests/rp/src/bin/dma_copy_async.rs index c53f644b..2c0b559a 100644 --- a/tests/rp/src/bin/dma_copy_async.rs +++ b/tests/rp/src/bin/dma_copy_async.rs @@ -1,6 +1,8 @@ #![no_std] #![no_main] #![feature(type_alias_impl_trait)] +#[path = "../common.rs"] +mod common; use defmt::{assert_eq, *}; use embassy_executor::Spawner; diff --git a/tests/rp/src/bin/flash.rs b/tests/rp/src/bin/flash.rs index 00bebe2b..cf9b86df 100644 --- a/tests/rp/src/bin/flash.rs +++ b/tests/rp/src/bin/flash.rs @@ -1,6 +1,8 @@ #![no_std] #![no_main] #![feature(type_alias_impl_trait)] +#[path = "../common.rs"] +mod common; use defmt::*; use embassy_executor::Spawner; diff --git a/tests/rp/src/bin/float.rs b/tests/rp/src/bin/float.rs index 6715271e..6a982507 100644 --- a/tests/rp/src/bin/float.rs +++ b/tests/rp/src/bin/float.rs @@ -1,6 +1,8 @@ #![no_std] #![no_main] #![feature(type_alias_impl_trait)] +#[path = "../common.rs"] +mod common; use defmt::*; use embassy_executor::Spawner; diff --git a/tests/rp/src/bin/gpio.rs b/tests/rp/src/bin/gpio.rs index 80e92d0f..51112d31 100644 --- a/tests/rp/src/bin/gpio.rs +++ b/tests/rp/src/bin/gpio.rs @@ -1,6 +1,8 @@ #![no_std] #![no_main] #![feature(type_alias_impl_trait)] +#[path = "../common.rs"] +mod common; use defmt::{assert, *}; use embassy_executor::Spawner; diff --git a/tests/rp/src/bin/gpio_async.rs b/tests/rp/src/bin/gpio_async.rs index f20b8fcb..532494de 100644 --- a/tests/rp/src/bin/gpio_async.rs +++ b/tests/rp/src/bin/gpio_async.rs @@ -1,6 +1,8 @@ #![no_std] #![no_main] #![feature(type_alias_impl_trait)] +#[path = "../common.rs"] +mod common; use defmt::{assert, *}; use embassy_executor::Spawner; diff --git a/tests/rp/src/bin/gpio_multicore.rs b/tests/rp/src/bin/gpio_multicore.rs index 6c13ccaa..780112bc 100644 --- a/tests/rp/src/bin/gpio_multicore.rs +++ b/tests/rp/src/bin/gpio_multicore.rs @@ -1,6 +1,8 @@ #![no_std] #![no_main] #![feature(type_alias_impl_trait)] +#[path = "../common.rs"] +mod common; use defmt::{info, unwrap}; use embassy_executor::Executor; diff --git a/tests/rp/src/bin/multicore.rs b/tests/rp/src/bin/multicore.rs index da78e887..114889de 100644 --- a/tests/rp/src/bin/multicore.rs +++ b/tests/rp/src/bin/multicore.rs @@ -1,6 +1,8 @@ #![no_std] #![no_main] #![feature(type_alias_impl_trait)] +#[path = "../common.rs"] +mod common; use defmt::{info, unwrap}; use embassy_executor::Executor; diff --git a/tests/rp/src/bin/pwm.rs b/tests/rp/src/bin/pwm.rs index b8cbe74c..c71d21ef 100644 --- a/tests/rp/src/bin/pwm.rs +++ b/tests/rp/src/bin/pwm.rs @@ -1,6 +1,8 @@ #![no_std] #![no_main] #![feature(type_alias_impl_trait)] +#[path = "../common.rs"] +mod common; use defmt::{assert, assert_eq, assert_ne, *}; use embassy_executor::Spawner; diff --git a/tests/rp/src/bin/spi.rs b/tests/rp/src/bin/spi.rs index 478d62ee..84dfa5a2 100644 --- a/tests/rp/src/bin/spi.rs +++ b/tests/rp/src/bin/spi.rs @@ -1,6 +1,8 @@ #![no_std] #![no_main] #![feature(type_alias_impl_trait)] +#[path = "../common.rs"] +mod common; use defmt::{assert_eq, *}; use embassy_executor::Spawner; diff --git a/tests/rp/src/bin/spi_async.rs b/tests/rp/src/bin/spi_async.rs index 2e22c9de..a4080b03 100644 --- a/tests/rp/src/bin/spi_async.rs +++ b/tests/rp/src/bin/spi_async.rs @@ -4,6 +4,8 @@ #![no_std] #![no_main] #![feature(type_alias_impl_trait)] +#[path = "../common.rs"] +mod common; use defmt::{assert_eq, *}; use embassy_executor::Spawner; diff --git a/tests/rp/src/bin/uart.rs b/tests/rp/src/bin/uart.rs index 80c18c02..2331c7d3 100644 --- a/tests/rp/src/bin/uart.rs +++ b/tests/rp/src/bin/uart.rs @@ -1,6 +1,8 @@ #![no_std] #![no_main] #![feature(type_alias_impl_trait)] +#[path = "../common.rs"] +mod common; use defmt::{assert_eq, *}; use embassy_executor::Spawner; diff --git a/tests/rp/src/bin/uart_buffered.rs b/tests/rp/src/bin/uart_buffered.rs index 1dcf57d0..e74e9986 100644 --- a/tests/rp/src/bin/uart_buffered.rs +++ b/tests/rp/src/bin/uart_buffered.rs @@ -1,6 +1,8 @@ #![no_std] #![no_main] #![feature(type_alias_impl_trait)] +#[path = "../common.rs"] +mod common; use defmt::{assert_eq, panic, *}; use embassy_executor::Spawner; diff --git a/tests/rp/src/bin/uart_dma.rs b/tests/rp/src/bin/uart_dma.rs index 75be76ed..fee6c825 100644 --- a/tests/rp/src/bin/uart_dma.rs +++ b/tests/rp/src/bin/uart_dma.rs @@ -1,6 +1,8 @@ #![no_std] #![no_main] #![feature(type_alias_impl_trait)] +#[path = "../common.rs"] +mod common; use defmt::{assert_eq, *}; use embassy_executor::Spawner; diff --git a/tests/rp/src/bin/uart_upgrade.rs b/tests/rp/src/bin/uart_upgrade.rs index 8605bb1c..760e5395 100644 --- a/tests/rp/src/bin/uart_upgrade.rs +++ b/tests/rp/src/bin/uart_upgrade.rs @@ -1,6 +1,8 @@ #![no_std] #![no_main] #![feature(type_alias_impl_trait)] +#[path = "../common.rs"] +mod common; use defmt::{assert_eq, *}; use embassy_executor::Spawner; diff --git a/tests/rp/src/common.rs b/tests/rp/src/common.rs new file mode 100644 index 00000000..955674f2 --- /dev/null +++ b/tests/rp/src/common.rs @@ -0,0 +1 @@ +teleprobe_meta::target!(b"rpi-pico"); diff --git a/tests/stm32/.cargo/config.toml b/tests/stm32/.cargo/config.toml index 426d6e67..07761b01 100644 --- a/tests/stm32/.cargo/config.toml +++ b/tests/stm32/.cargo/config.toml @@ -3,7 +3,7 @@ build-std = ["core"] build-std-features = ["panic_immediate_abort"] [target.'cfg(all(target_arch = "arm", target_os = "none"))'] -runner = "teleprobe client run --target bluepill-stm32f103c8 --elf" +runner = "teleprobe client run" #runner = "teleprobe local run --chip STM32F103C8 --elf" rustflags = [ diff --git a/tests/stm32/Cargo.toml b/tests/stm32/Cargo.toml index 4c059774..f1b0ba12 100644 --- a/tests/stm32/Cargo.toml +++ b/tests/stm32/Cargo.toml @@ -22,6 +22,8 @@ ble = [] not-gpdma = [] [dependencies] +teleprobe-meta = "1" + embassy-sync = { version = "0.2.0", path = "../../embassy-sync", features = ["defmt"] } embassy-executor = { version = "0.2.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } embassy-time = { version = "0.1.0", path = "../../embassy-time", features = ["defmt", "tick-hz-32_768", "defmt-timestamp-uptime"] } diff --git a/tests/stm32/build.rs b/tests/stm32/build.rs index b4583147..2e71954d 100644 --- a/tests/stm32/build.rs +++ b/tests/stm32/build.rs @@ -26,6 +26,7 @@ fn main() -> Result<(), Box> { } println!("cargo:rustc-link-arg-bins=-Tdefmt.x"); + println!("cargo:rustc-link-arg-bins=-Tteleprobe.x"); Ok(()) } diff --git a/tests/stm32/src/bin/gpio.rs b/tests/stm32/src/bin/gpio.rs index 8b99b08a..67f44317 100644 --- a/tests/stm32/src/bin/gpio.rs +++ b/tests/stm32/src/bin/gpio.rs @@ -1,13 +1,13 @@ #![no_std] #![no_main] #![feature(type_alias_impl_trait)] +#[path = "../common.rs"] +mod common; -#[path = "../example_common.rs"] -mod example_common; +use common::*; use defmt::assert; use embassy_executor::Spawner; use embassy_stm32::gpio::{Flex, Input, Level, Output, OutputOpenDrain, Pull, Speed}; -use example_common::*; #[embassy_executor::main] async fn main(_spawner: Spawner) { diff --git a/tests/stm32/src/bin/rtc.rs b/tests/stm32/src/bin/rtc.rs index ccf2ca60..32d35c42 100644 --- a/tests/stm32/src/bin/rtc.rs +++ b/tests/stm32/src/bin/rtc.rs @@ -1,18 +1,16 @@ #![no_std] #![no_main] #![feature(type_alias_impl_trait)] +#[path = "../common.rs"] +mod common; -// required-features: chrono - -#[path = "../example_common.rs"] -mod example_common; use chrono::{NaiveDate, NaiveDateTime}; +use common::*; use defmt::assert; use embassy_executor::Spawner; use embassy_stm32::pac; use embassy_stm32::rtc::{Rtc, RtcConfig}; use embassy_time::{Duration, Timer}; -use example_common::*; #[embassy_executor::main] async fn main(_spawner: Spawner) { diff --git a/tests/stm32/src/bin/sdmmc.rs b/tests/stm32/src/bin/sdmmc.rs index 7d96cf41..51502538 100644 --- a/tests/stm32/src/bin/sdmmc.rs +++ b/tests/stm32/src/bin/sdmmc.rs @@ -2,6 +2,8 @@ #![no_std] #![no_main] #![feature(type_alias_impl_trait)] +#[path = "../common.rs"] +mod common; use defmt::{assert_eq, *}; use embassy_executor::Spawner; diff --git a/tests/stm32/src/bin/spi.rs b/tests/stm32/src/bin/spi.rs index a87ac323..819ecae3 100644 --- a/tests/stm32/src/bin/spi.rs +++ b/tests/stm32/src/bin/spi.rs @@ -1,15 +1,15 @@ #![no_std] #![no_main] #![feature(type_alias_impl_trait)] +#[path = "../common.rs"] +mod common; -#[path = "../example_common.rs"] -mod example_common; +use common::*; use defmt::assert_eq; use embassy_executor::Spawner; use embassy_stm32::dma::NoDma; use embassy_stm32::spi::{self, Spi}; use embassy_stm32::time::Hertz; -use example_common::*; #[embassy_executor::main] async fn main(_spawner: Spawner) { diff --git a/tests/stm32/src/bin/spi_dma.rs b/tests/stm32/src/bin/spi_dma.rs index 74776ebf..78aad24e 100644 --- a/tests/stm32/src/bin/spi_dma.rs +++ b/tests/stm32/src/bin/spi_dma.rs @@ -1,14 +1,14 @@ #![no_std] #![no_main] #![feature(type_alias_impl_trait)] +#[path = "../common.rs"] +mod common; -#[path = "../example_common.rs"] -mod example_common; +use common::*; use defmt::assert_eq; use embassy_executor::Spawner; use embassy_stm32::spi::{self, Spi}; use embassy_stm32::time::Hertz; -use example_common::*; #[embassy_executor::main] async fn main(_spawner: Spawner) { diff --git a/tests/stm32/src/bin/timer.rs b/tests/stm32/src/bin/timer.rs index e00e43bf..f8b453cd 100644 --- a/tests/stm32/src/bin/timer.rs +++ b/tests/stm32/src/bin/timer.rs @@ -1,13 +1,13 @@ #![no_std] #![no_main] #![feature(type_alias_impl_trait)] +#[path = "../common.rs"] +mod common; -#[path = "../example_common.rs"] -mod example_common; +use common::*; use defmt::assert; use embassy_executor::Spawner; use embassy_time::{Duration, Instant, Timer}; -use example_common::*; #[embassy_executor::main] async fn main(_spawner: Spawner) { diff --git a/tests/stm32/src/bin/tl_mbox.rs b/tests/stm32/src/bin/tl_mbox.rs index 626e7ac6..fab9f0e1 100644 --- a/tests/stm32/src/bin/tl_mbox.rs +++ b/tests/stm32/src/bin/tl_mbox.rs @@ -1,16 +1,16 @@ +// required-features: ble + #![no_std] #![no_main] #![feature(type_alias_impl_trait)] +#[path = "../common.rs"] +mod common; -// required-features: ble - -#[path = "../example_common.rs"] -mod example_common; +use common::*; use embassy_executor::Spawner; use embassy_stm32::tl_mbox::{Config, TlMbox}; use embassy_stm32::{bind_interrupts, tl_mbox}; use embassy_time::{Duration, Timer}; -use example_common::*; bind_interrupts!(struct Irqs{ IPCC_C1_RX => tl_mbox::ReceiveInterruptHandler; diff --git a/tests/stm32/src/bin/usart.rs b/tests/stm32/src/bin/usart.rs index 415c7afa..394005b8 100644 --- a/tests/stm32/src/bin/usart.rs +++ b/tests/stm32/src/bin/usart.rs @@ -1,16 +1,16 @@ #![no_std] #![no_main] #![feature(type_alias_impl_trait)] +#[path = "../common.rs"] +mod common; -#[path = "../example_common.rs"] -mod example_common; +use common::*; use defmt::assert_eq; use embassy_executor::Spawner; use embassy_stm32::dma::NoDma; use embassy_stm32::usart::{Config, Error, Uart}; use embassy_stm32::{bind_interrupts, peripherals, usart}; use embassy_time::{Duration, Instant}; -use example_common::*; #[cfg(any( feature = "stm32f103c8", diff --git a/tests/stm32/src/bin/usart_dma.rs b/tests/stm32/src/bin/usart_dma.rs index 7f002b97..50dd2893 100644 --- a/tests/stm32/src/bin/usart_dma.rs +++ b/tests/stm32/src/bin/usart_dma.rs @@ -1,15 +1,15 @@ #![no_std] #![no_main] #![feature(type_alias_impl_trait)] +#[path = "../common.rs"] +mod common; -#[path = "../example_common.rs"] -mod example_common; +use common::*; use defmt::assert_eq; use embassy_executor::Spawner; use embassy_futures::join::join; use embassy_stm32::usart::{Config, Uart}; use embassy_stm32::{bind_interrupts, peripherals, usart}; -use example_common::*; #[cfg(any( feature = "stm32f103c8", diff --git a/tests/stm32/src/bin/usart_rx_ringbuffered.rs b/tests/stm32/src/bin/usart_rx_ringbuffered.rs index 3a34773f..c8dd2643 100644 --- a/tests/stm32/src/bin/usart_rx_ringbuffered.rs +++ b/tests/stm32/src/bin/usart_rx_ringbuffered.rs @@ -3,15 +3,15 @@ #![no_std] #![no_main] #![feature(type_alias_impl_trait)] +#[path = "../common.rs"] +mod common; -#[path = "../example_common.rs"] -mod example_common; +use common::*; use defmt::{assert_eq, panic}; use embassy_executor::Spawner; use embassy_stm32::usart::{Config, DataBits, Parity, RingBufferedUartRx, StopBits, Uart, UartTx}; use embassy_stm32::{bind_interrupts, peripherals, usart}; use embassy_time::{Duration, Timer}; -use example_common::*; use rand_chacha::ChaCha8Rng; use rand_core::{RngCore, SeedableRng}; diff --git a/tests/stm32/src/common.rs b/tests/stm32/src/common.rs new file mode 100644 index 00000000..3d2a9b8e --- /dev/null +++ b/tests/stm32/src/common.rs @@ -0,0 +1,44 @@ +#![macro_use] + +pub use defmt::*; +#[allow(unused)] +use embassy_stm32::time::Hertz; +use embassy_stm32::Config; +use {defmt_rtt as _, panic_probe as _}; + +#[cfg(feature = "stm32f103c8")] +teleprobe_meta::target!(b"bluepill-stm32f103c8"); +#[cfg(feature = "stm32g491re")] +teleprobe_meta::target!(b"nucleo-stm32g491re"); +#[cfg(feature = "stm32g071rb")] +teleprobe_meta::target!(b"nucleo-stm32g071rb"); +#[cfg(feature = "stm32f429zi")] +teleprobe_meta::target!(b"nucleo-stm32f429zi"); +#[cfg(feature = "stm32wb55rg")] +teleprobe_meta::target!(b"nucleo-stm32wb55rg"); +#[cfg(feature = "stm32h755zi")] +teleprobe_meta::target!(b"nucleo-stm32h755zi"); +#[cfg(feature = "stm32u585ai")] +teleprobe_meta::target!(b"iot-stm32u585ai"); +#[cfg(feature = "stm32h563zi")] +teleprobe_meta::target!(b"nucleo-stm32h563zi"); +#[cfg(feature = "stm32c031c6")] +teleprobe_meta::target!(b"nucleo-stm32c031c6"); + +pub fn config() -> Config { + #[allow(unused_mut)] + let mut config = Config::default(); + + #[cfg(feature = "stm32h755zi")] + { + config.rcc.sys_ck = Some(Hertz(400_000_000)); + config.rcc.pll1.q_ck = Some(Hertz(100_000_000)); + } + + #[cfg(feature = "stm32u585ai")] + { + config.rcc.mux = embassy_stm32::rcc::ClockSrc::MSI(embassy_stm32::rcc::MSIRange::Range48mhz); + } + + config +} diff --git a/tests/stm32/src/example_common.rs b/tests/stm32/src/example_common.rs deleted file mode 100644 index 3d150da6..00000000 --- a/tests/stm32/src/example_common.rs +++ /dev/null @@ -1,25 +0,0 @@ -#![macro_use] - -pub use defmt::*; -#[allow(unused)] -use embassy_stm32::time::Hertz; -use embassy_stm32::Config; -use {defmt_rtt as _, panic_probe as _}; - -pub fn config() -> Config { - #[allow(unused_mut)] - let mut config = Config::default(); - - #[cfg(feature = "stm32h755zi")] - { - config.rcc.sys_ck = Some(Hertz(400_000_000)); - config.rcc.pll1.q_ck = Some(Hertz(100_000_000)); - } - - #[cfg(feature = "stm32u585ai")] - { - config.rcc.mux = embassy_stm32::rcc::ClockSrc::MSI(embassy_stm32::rcc::MSIRange::Range48mhz); - } - - config -} From 5205b5b09588d6e0006e5479764ee94b113b03b9 Mon Sep 17 00:00:00 2001 From: Rasmus Melchior Jacobsen Date: Tue, 30 May 2023 13:36:42 +0200 Subject: [PATCH 159/178] Split FirmwareUpdater into async and blocking types --- .../boot/src/firmware_updater/asynch.rs | 176 +++++++++-------- .../boot/src/firmware_updater/blocking.rs | 187 +++++++++++------- embassy-boot/boot/src/firmware_updater/mod.rs | 51 ++--- embassy-boot/boot/src/lib.rs | 4 +- 4 files changed, 236 insertions(+), 182 deletions(-) diff --git a/embassy-boot/boot/src/firmware_updater/asynch.rs b/embassy-boot/boot/src/firmware_updater/asynch.rs index bdd03bff..d0780bdf 100644 --- a/embassy-boot/boot/src/firmware_updater/asynch.rs +++ b/embassy-boot/boot/src/firmware_updater/asynch.rs @@ -1,20 +1,68 @@ use digest::Digest; -use embedded_storage_async::nor_flash::NorFlash as AsyncNorFlash; +use embassy_embedded_hal::flash::partition::Partition; +use embassy_sync::blocking_mutex::raw::NoopRawMutex; +use embedded_storage_async::nor_flash::NorFlash; -use crate::{FirmwareUpdater, FirmwareUpdaterError, Partition, State, BOOT_MAGIC, SWAP_MAGIC}; +use super::FirmwareUpdaterConfig; +use crate::{FirmwareUpdaterError, State, BOOT_MAGIC, STATE_ERASE_VALUE, SWAP_MAGIC}; + +/// FirmwareUpdater is an application API for interacting with the BootLoader without the ability to +/// 'mess up' the internal bootloader state +pub struct FirmwareUpdater { + dfu: DFU, + state: STATE, +} + +impl<'a, FLASH: NorFlash> + FirmwareUpdaterConfig, Partition<'a, NoopRawMutex, FLASH>> +{ + /// Create a firmware updater config from the flash and address symbols defined in the linkerfile + #[cfg(target_os = "none")] + pub fn from_linkerfile(flash: &'a Mutex) -> Self { + use embassy_sync::mutex::Mutex; + + extern "C" { + static __bootloader_state_start: u32; + static __bootloader_state_end: u32; + static __bootloader_dfu_start: u32; + static __bootloader_dfu_end: u32; + } + + let dfu = unsafe { + let start = &__bootloader_dfu_start as *const u32 as u32; + let end = &__bootloader_dfu_end as *const u32 as u32; + trace!("DFU: 0x{:x} - 0x{:x}", start, end); + + Partition::new(flash, start, end - start) + }; + let state = unsafe { + let start = &__bootloader_state_start as *const u32 as u32; + let end = &__bootloader_state_end as *const u32 as u32; + trace!("STATE: 0x{:x} - 0x{:x}", start, end); + + Partition::new(flash, start, end - start) + }; + + Self { dfu, state } + } +} + +impl FirmwareUpdater { + /// Create a firmware updater instance with partition ranges for the update and state partitions. + pub fn new(config: FirmwareUpdaterConfig) -> Self { + Self { + dfu: config.dfu, + state: config.state, + } + } -impl FirmwareUpdater { /// Obtain the current state. /// /// This is useful to check if the bootloader has just done a swap, in order /// to do verifications and self-tests of the new image before calling /// `mark_booted`. - pub async fn get_state( - &mut self, - state_flash: &mut F, - aligned: &mut [u8], - ) -> Result { - self.state.read(state_flash, 0, aligned).await?; + pub async fn get_state(&mut self, aligned: &mut [u8]) -> Result { + self.state.read(0, aligned).await?; if !aligned.iter().any(|&b| b != SWAP_MAGIC) { Ok(State::Swap) @@ -37,19 +85,18 @@ impl FirmwareUpdater { /// /// # Safety /// - /// The `_aligned` buffer must have a size of F::WRITE_SIZE, and follow the alignment rules for the flash being read from + /// The `_aligned` buffer must have a size of STATE::WRITE_SIZE, and follow the alignment rules for the flash being read from /// and written to. - #[cfg(all(feature = "_verify", feature = "nightly"))] - pub async fn verify_and_mark_updated( + #[cfg(feature = "_verify")] + pub async fn verify_and_mark_updated( &mut self, - _state_and_dfu_flash: &mut F, _public_key: &[u8], _signature: &[u8], _update_len: u32, _aligned: &mut [u8], ) -> Result<(), FirmwareUpdaterError> { - assert_eq!(_aligned.len(), F::WRITE_SIZE); - assert!(_update_len <= self.dfu.size()); + assert_eq!(_aligned.len(), STATE::WRITE_SIZE); + assert!(_update_len <= self.dfu.capacity() as u32); #[cfg(feature = "ed25519-dalek")] { @@ -63,8 +110,7 @@ impl FirmwareUpdater { let signature = Signature::from_bytes(_signature).map_err(into_signature_error)?; let mut message = [0; 64]; - self.hash::<_, Sha512>(_state_and_dfu_flash, _update_len, _aligned, &mut message) - .await?; + self.hash::(_update_len, _aligned, &mut message).await?; public_key.verify(&message, &signature).map_err(into_signature_error)? } @@ -85,8 +131,7 @@ impl FirmwareUpdater { let signature = Signature::try_from(&signature).map_err(into_signature_error)?; let mut message = [0; 64]; - self.hash::<_, Sha512>(_state_and_dfu_flash, _update_len, _aligned, &mut message) - .await?; + self.hash::(_update_len, _aligned, &mut message).await?; let r = public_key.verify(&message, &signature); trace!( @@ -99,20 +144,19 @@ impl FirmwareUpdater { r.map_err(into_signature_error)? } - self.set_magic(_aligned, SWAP_MAGIC, _state_and_dfu_flash).await + self.set_magic(_aligned, SWAP_MAGIC).await } /// Verify the update in DFU with any digest. - pub async fn hash( + pub async fn hash( &mut self, - dfu_flash: &mut F, update_len: u32, chunk_buf: &mut [u8], output: &mut [u8], ) -> Result<(), FirmwareUpdaterError> { let mut digest = D::new(); for offset in (0..update_len).step_by(chunk_buf.len()) { - self.dfu.read(dfu_flash, offset, chunk_buf).await?; + self.dfu.read(offset, chunk_buf).await?; let len = core::cmp::min((update_len - offset) as usize, chunk_buf.len()); digest.update(&chunk_buf[..len]); } @@ -124,60 +168,44 @@ impl FirmwareUpdater { /// /// # Safety /// - /// The `aligned` buffer must have a size of F::WRITE_SIZE, and follow the alignment rules for the flash being written to. - #[cfg(all(feature = "nightly", not(feature = "_verify")))] - pub async fn mark_updated( - &mut self, - state_flash: &mut F, - aligned: &mut [u8], - ) -> Result<(), FirmwareUpdaterError> { - assert_eq!(aligned.len(), F::WRITE_SIZE); - self.set_magic(aligned, SWAP_MAGIC, state_flash).await + /// The `aligned` buffer must have a size of STATE::WRITE_SIZE, and follow the alignment rules for the flash being written to. + #[cfg(not(feature = "_verify"))] + pub async fn mark_updated(&mut self, aligned: &mut [u8]) -> Result<(), FirmwareUpdaterError> { + assert_eq!(aligned.len(), STATE::WRITE_SIZE); + self.set_magic(aligned, SWAP_MAGIC).await } /// Mark firmware boot successful and stop rollback on reset. /// /// # Safety /// - /// The `aligned` buffer must have a size of F::WRITE_SIZE, and follow the alignment rules for the flash being written to. - pub async fn mark_booted( - &mut self, - state_flash: &mut F, - aligned: &mut [u8], - ) -> Result<(), FirmwareUpdaterError> { - assert_eq!(aligned.len(), F::WRITE_SIZE); - self.set_magic(aligned, BOOT_MAGIC, state_flash).await + /// The `aligned` buffer must have a size of STATE::WRITE_SIZE, and follow the alignment rules for the flash being written to. + pub async fn mark_booted(&mut self, aligned: &mut [u8]) -> Result<(), FirmwareUpdaterError> { + assert_eq!(aligned.len(), STATE::WRITE_SIZE); + self.set_magic(aligned, BOOT_MAGIC).await } - async fn set_magic( - &mut self, - aligned: &mut [u8], - magic: u8, - state_flash: &mut F, - ) -> Result<(), FirmwareUpdaterError> { - self.state.read(state_flash, 0, aligned).await?; + async fn set_magic(&mut self, aligned: &mut [u8], magic: u8) -> Result<(), FirmwareUpdaterError> { + self.state.read(0, aligned).await?; if aligned.iter().any(|&b| b != magic) { // Read progress validity - self.state.read(state_flash, F::WRITE_SIZE as u32, aligned).await?; - - // FIXME: Do not make this assumption. - const STATE_ERASE_VALUE: u8 = 0xFF; + self.state.read(STATE::WRITE_SIZE as u32, aligned).await?; if aligned.iter().any(|&b| b != STATE_ERASE_VALUE) { // The current progress validity marker is invalid } else { // Invalidate progress aligned.fill(!STATE_ERASE_VALUE); - self.state.write(state_flash, F::WRITE_SIZE as u32, aligned).await?; + self.state.write(STATE::WRITE_SIZE as u32, aligned).await?; } // Clear magic and progress - self.state.wipe(state_flash).await?; + self.state.erase(0, self.state.capacity() as u32).await?; // Set magic aligned.fill(magic); - self.state.write(state_flash, 0, aligned).await?; + self.state.write(0, aligned).await?; } Ok(()) } @@ -189,19 +217,12 @@ impl FirmwareUpdater { /// # Safety /// /// Failing to meet alignment and size requirements may result in a panic. - pub async fn write_firmware( - &mut self, - offset: usize, - data: &[u8], - dfu_flash: &mut F, - ) -> Result<(), FirmwareUpdaterError> { - assert!(data.len() >= F::ERASE_SIZE); + pub async fn write_firmware(&mut self, offset: usize, data: &[u8]) -> Result<(), FirmwareUpdaterError> { + assert!(data.len() >= DFU::ERASE_SIZE); - self.dfu - .erase(dfu_flash, offset as u32, (offset + data.len()) as u32) - .await?; + self.dfu.erase(offset as u32, (offset + data.len()) as u32).await?; - self.dfu.write(dfu_flash, offset as u32, data).await?; + self.dfu.write(offset as u32, data).await?; Ok(()) } @@ -211,18 +232,18 @@ impl FirmwareUpdater { /// /// Using this instead of `write_firmware` allows for an optimized API in /// exchange for added complexity. - pub async fn prepare_update( - &mut self, - dfu_flash: &mut F, - ) -> Result { - self.dfu.wipe(dfu_flash).await?; + pub async fn prepare_update(&mut self) -> Result<&mut DFU, FirmwareUpdaterError> { + self.dfu.erase(0, self.dfu.capacity() as u32).await?; - Ok(self.dfu) + Ok(&mut self.dfu) } } #[cfg(test)] mod tests { + use embassy_embedded_hal::flash::partition::Partition; + use embassy_sync::blocking_mutex::raw::NoopRawMutex; + use embassy_sync::mutex::Mutex; use futures::executor::block_on; use sha1::{Digest, Sha1}; @@ -231,20 +252,19 @@ mod tests { #[test] fn can_verify_sha1() { - const STATE: Partition = Partition::new(0, 4096); - const DFU: Partition = Partition::new(65536, 131072); - - let mut flash = MemFlash::<131072, 4096, 8>::default(); + let flash = Mutex::::new(MemFlash::<131072, 4096, 8>::default()); + let state = Partition::new(&flash, 0, 4096); + let dfu = Partition::new(&flash, 65536, 65536); let update = [0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66]; let mut to_write = [0; 4096]; to_write[..7].copy_from_slice(update.as_slice()); - let mut updater = FirmwareUpdater::new(DFU, STATE); - block_on(updater.write_firmware(0, to_write.as_slice(), &mut flash)).unwrap(); + let mut updater = FirmwareUpdater::new(FirmwareUpdaterConfig { dfu, state }); + block_on(updater.write_firmware(0, to_write.as_slice())).unwrap(); let mut chunk_buf = [0; 2]; let mut hash = [0; 20]; - block_on(updater.hash::<_, Sha1>(&mut flash, update.len() as u32, &mut chunk_buf, &mut hash)).unwrap(); + block_on(updater.hash::(update.len() as u32, &mut chunk_buf, &mut hash)).unwrap(); assert_eq!(Sha1::digest(update).as_slice(), hash); } diff --git a/embassy-boot/boot/src/firmware_updater/blocking.rs b/embassy-boot/boot/src/firmware_updater/blocking.rs index 50caaf08..c4412614 100644 --- a/embassy-boot/boot/src/firmware_updater/blocking.rs +++ b/embassy-boot/boot/src/firmware_updater/blocking.rs @@ -1,25 +1,70 @@ use digest::Digest; +use embassy_embedded_hal::flash::partition::BlockingPartition; +use embassy_sync::blocking_mutex::raw::NoopRawMutex; use embedded_storage::nor_flash::NorFlash; -use crate::{FirmwareUpdater, FirmwareUpdaterError, Partition, State, BOOT_MAGIC, SWAP_MAGIC}; +use super::FirmwareUpdaterConfig; +use crate::{FirmwareUpdaterError, State, BOOT_MAGIC, STATE_ERASE_VALUE, SWAP_MAGIC}; + +/// Blocking FirmwareUpdater is an application API for interacting with the BootLoader without the ability to +/// 'mess up' the internal bootloader state +pub struct BlockingFirmwareUpdater { + dfu: DFU, + state: STATE, +} + +impl<'a, FLASH: NorFlash> + FirmwareUpdaterConfig, BlockingPartition<'a, NoopRawMutex, FLASH>> +{ + /// Create a firmware updater config from the flash and address symbols defined in the linkerfile + #[cfg(target_os = "none")] + pub fn from_linkerfile_blocking(flash: &'a Mutex>) -> Self { + use core::cell::RefCell; + + use embassy_sync::blocking_mutex::Mutex; + + extern "C" { + static __bootloader_state_start: u32; + static __bootloader_state_end: u32; + static __bootloader_dfu_start: u32; + static __bootloader_dfu_end: u32; + } + + let dfu = unsafe { + let start = &__bootloader_dfu_start as *const u32 as u32; + let end = &__bootloader_dfu_end as *const u32 as u32; + trace!("DFU: 0x{:x} - 0x{:x}", start, end); + + BlockingPartition::new(flash, start, end - start) + }; + let state = unsafe { + let start = &__bootloader_state_start as *const u32 as u32; + let end = &__bootloader_state_end as *const u32 as u32; + trace!("STATE: 0x{:x} - 0x{:x}", start, end); + + BlockingPartition::new(flash, start, end - start) + }; -impl FirmwareUpdater { - /// Create a firmware updater instance with partition ranges for the update and state partitions. - pub const fn new(dfu: Partition, state: Partition) -> Self { Self { dfu, state } } +} + +impl BlockingFirmwareUpdater { + /// Create a firmware updater instance with partition ranges for the update and state partitions. + pub fn new(config: FirmwareUpdaterConfig) -> Self { + Self { + dfu: config.dfu, + state: config.state, + } + } /// Obtain the current state. /// /// This is useful to check if the bootloader has just done a swap, in order /// to do verifications and self-tests of the new image before calling /// `mark_booted`. - pub fn get_state_blocking( - &mut self, - state_flash: &mut F, - aligned: &mut [u8], - ) -> Result { - self.state.read_blocking(state_flash, 0, aligned)?; + pub fn get_state(&mut self, aligned: &mut [u8]) -> Result { + self.state.read(0, aligned)?; if !aligned.iter().any(|&b| b != SWAP_MAGIC) { Ok(State::Swap) @@ -42,19 +87,18 @@ impl FirmwareUpdater { /// /// # Safety /// - /// The `_aligned` buffer must have a size of F::WRITE_SIZE, and follow the alignment rules for the flash being read from + /// The `_aligned` buffer must have a size of STATE::WRITE_SIZE, and follow the alignment rules for the flash being read from /// and written to. #[cfg(feature = "_verify")] - pub fn verify_and_mark_updated_blocking( + pub fn verify_and_mark_updated( &mut self, - _state_and_dfu_flash: &mut F, _public_key: &[u8], _signature: &[u8], _update_len: u32, _aligned: &mut [u8], ) -> Result<(), FirmwareUpdaterError> { - assert_eq!(_aligned.len(), F::WRITE_SIZE); - assert!(_update_len <= self.dfu.size()); + assert_eq!(_aligned.len(), STATE::WRITE_SIZE); + assert!(_update_len <= self.dfu.capacity() as u32); #[cfg(feature = "ed25519-dalek")] { @@ -68,7 +112,7 @@ impl FirmwareUpdater { let signature = Signature::from_bytes(_signature).map_err(into_signature_error)?; let mut message = [0; 64]; - self.hash_blocking::<_, Sha512>(_state_and_dfu_flash, _update_len, _aligned, &mut message)?; + self.hash::(_update_len, _aligned, &mut message)?; public_key.verify(&message, &signature).map_err(into_signature_error)? } @@ -89,7 +133,7 @@ impl FirmwareUpdater { let signature = Signature::try_from(&signature).map_err(into_signature_error)?; let mut message = [0; 64]; - self.hash_blocking::<_, Sha512>(_state_and_dfu_flash, _update_len, _aligned, &mut message)?; + self.hash::(_update_len, _aligned, &mut message)?; let r = public_key.verify(&message, &signature); trace!( @@ -102,20 +146,19 @@ impl FirmwareUpdater { r.map_err(into_signature_error)? } - self.set_magic_blocking(_aligned, SWAP_MAGIC, _state_and_dfu_flash) + self.set_magic(_aligned, SWAP_MAGIC) } /// Verify the update in DFU with any digest. - pub fn hash_blocking( + pub fn hash( &mut self, - dfu_flash: &mut F, update_len: u32, chunk_buf: &mut [u8], output: &mut [u8], ) -> Result<(), FirmwareUpdaterError> { let mut digest = D::new(); for offset in (0..update_len).step_by(chunk_buf.len()) { - self.dfu.read_blocking(dfu_flash, offset, chunk_buf)?; + self.dfu.read(offset, chunk_buf)?; let len = core::cmp::min((update_len - offset) as usize, chunk_buf.len()); digest.update(&chunk_buf[..len]); } @@ -127,60 +170,44 @@ impl FirmwareUpdater { /// /// # Safety /// - /// The `aligned` buffer must have a size of F::WRITE_SIZE, and follow the alignment rules for the flash being written to. + /// The `aligned` buffer must have a size of STATE::WRITE_SIZE, and follow the alignment rules for the flash being written to. #[cfg(not(feature = "_verify"))] - pub fn mark_updated_blocking( - &mut self, - state_flash: &mut F, - aligned: &mut [u8], - ) -> Result<(), FirmwareUpdaterError> { - assert_eq!(aligned.len(), F::WRITE_SIZE); - self.set_magic_blocking(aligned, SWAP_MAGIC, state_flash) + pub fn mark_updated(&mut self, aligned: &mut [u8]) -> Result<(), FirmwareUpdaterError> { + assert_eq!(aligned.len(), STATE::WRITE_SIZE); + self.set_magic(aligned, SWAP_MAGIC) } /// Mark firmware boot successful and stop rollback on reset. /// /// # Safety /// - /// The `aligned` buffer must have a size of F::WRITE_SIZE, and follow the alignment rules for the flash being written to. - pub fn mark_booted_blocking( - &mut self, - state_flash: &mut F, - aligned: &mut [u8], - ) -> Result<(), FirmwareUpdaterError> { - assert_eq!(aligned.len(), F::WRITE_SIZE); - self.set_magic_blocking(aligned, BOOT_MAGIC, state_flash) + /// The `aligned` buffer must have a size of STATE::WRITE_SIZE, and follow the alignment rules for the flash being written to. + pub fn mark_booted(&mut self, aligned: &mut [u8]) -> Result<(), FirmwareUpdaterError> { + assert_eq!(aligned.len(), STATE::WRITE_SIZE); + self.set_magic(aligned, BOOT_MAGIC) } - fn set_magic_blocking( - &mut self, - aligned: &mut [u8], - magic: u8, - state_flash: &mut F, - ) -> Result<(), FirmwareUpdaterError> { - self.state.read_blocking(state_flash, 0, aligned)?; + fn set_magic(&mut self, aligned: &mut [u8], magic: u8) -> Result<(), FirmwareUpdaterError> { + self.state.read(0, aligned)?; if aligned.iter().any(|&b| b != magic) { // Read progress validity - self.state.read_blocking(state_flash, F::WRITE_SIZE as u32, aligned)?; - - // FIXME: Do not make this assumption. - const STATE_ERASE_VALUE: u8 = 0xFF; + self.state.read(STATE::WRITE_SIZE as u32, aligned)?; if aligned.iter().any(|&b| b != STATE_ERASE_VALUE) { // The current progress validity marker is invalid } else { // Invalidate progress aligned.fill(!STATE_ERASE_VALUE); - self.state.write_blocking(state_flash, F::WRITE_SIZE as u32, aligned)?; + self.state.write(STATE::WRITE_SIZE as u32, aligned)?; } // Clear magic and progress - self.state.wipe_blocking(state_flash)?; + self.state.erase(0, self.state.capacity() as u32)?; // Set magic aligned.fill(magic); - self.state.write_blocking(state_flash, 0, aligned)?; + self.state.write(0, aligned)?; } Ok(()) } @@ -192,18 +219,12 @@ impl FirmwareUpdater { /// # Safety /// /// Failing to meet alignment and size requirements may result in a panic. - pub fn write_firmware_blocking( - &mut self, - offset: usize, - data: &[u8], - dfu_flash: &mut F, - ) -> Result<(), FirmwareUpdaterError> { - assert!(data.len() >= F::ERASE_SIZE); + pub fn write_firmware(&mut self, offset: usize, data: &[u8]) -> Result<(), FirmwareUpdaterError> { + assert!(data.len() >= DFU::ERASE_SIZE); - self.dfu - .erase_blocking(dfu_flash, offset as u32, (offset + data.len()) as u32)?; + self.dfu.erase(offset as u32, (offset + data.len()) as u32)?; - self.dfu.write_blocking(dfu_flash, offset as u32, data)?; + self.dfu.write(offset as u32, data)?; Ok(()) } @@ -211,11 +232,45 @@ impl FirmwareUpdater { /// Prepare for an incoming DFU update by erasing the entire DFU area and /// returning its `Partition`. /// - /// Using this instead of `write_firmware_blocking` allows for an optimized - /// API in exchange for added complexity. - pub fn prepare_update_blocking(&mut self, flash: &mut F) -> Result { - self.dfu.wipe_blocking(flash)?; + /// Using this instead of `write_firmware` allows for an optimized API in + /// exchange for added complexity. + pub fn prepare_update(&mut self) -> Result<&mut DFU, FirmwareUpdaterError> { + self.dfu.erase(0, self.dfu.capacity() as u32)?; - Ok(self.dfu) + Ok(&mut self.dfu) + } +} + +#[cfg(test)] +mod tests { + use core::cell::RefCell; + + use embassy_embedded_hal::flash::partition::BlockingPartition; + use embassy_sync::blocking_mutex::raw::NoopRawMutex; + use embassy_sync::blocking_mutex::Mutex; + use sha1::{Digest, Sha1}; + + use super::*; + use crate::mem_flash::MemFlash; + + #[test] + fn can_verify_sha1() { + let flash = Mutex::::new(RefCell::new(MemFlash::<131072, 4096, 8>::default())); + let state = BlockingPartition::new(&flash, 0, 4096); + let dfu = BlockingPartition::new(&flash, 65536, 65536); + + let update = [0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66]; + let mut to_write = [0; 4096]; + to_write[..7].copy_from_slice(update.as_slice()); + + let mut updater = BlockingFirmwareUpdater::new(FirmwareUpdaterConfig { dfu, state }); + updater.write_firmware(0, to_write.as_slice()).unwrap(); + let mut chunk_buf = [0; 2]; + let mut hash = [0; 20]; + updater + .hash::(update.len() as u32, &mut chunk_buf, &mut hash) + .unwrap(); + + assert_eq!(Sha1::digest(update).as_slice(), hash); } } diff --git a/embassy-boot/boot/src/firmware_updater/mod.rs b/embassy-boot/boot/src/firmware_updater/mod.rs index e09f5eb6..a37984a3 100644 --- a/embassy-boot/boot/src/firmware_updater/mod.rs +++ b/embassy-boot/boot/src/firmware_updater/mod.rs @@ -2,9 +2,22 @@ mod asynch; mod blocking; +#[cfg(feature = "nightly")] +pub use asynch::FirmwareUpdater; +pub use blocking::BlockingFirmwareUpdater; use embedded_storage::nor_flash::{NorFlashError, NorFlashErrorKind}; -use crate::Partition; +/// Firmware updater flash configuration holding the two flashes used by the updater +/// +/// If only a single flash is actually used, then that flash should be partitioned into two partitions before use. +/// The easiest way to do this is to use [`FirmwareUpdaterConfig::from_linkerfile`] or [`FirmwareUpdaterConfig::from_linkerfile_blocking`] which will partition +/// the provided flash according to symbols defined in the linkerfile. +pub struct FirmwareUpdaterConfig { + /// The dfu flash partition + pub dfu: DFU, + /// The state flash partition + pub state: STATE, +} /// Errors returned by FirmwareUpdater #[derive(Debug)] @@ -33,39 +46,3 @@ where FirmwareUpdaterError::Flash(error.kind()) } } - -/// FirmwareUpdater is an application API for interacting with the BootLoader without the ability to -/// 'mess up' the internal bootloader state -pub struct FirmwareUpdater { - state: Partition, - dfu: Partition, -} - -#[cfg(target_os = "none")] -impl Default for FirmwareUpdater { - fn default() -> Self { - extern "C" { - static __bootloader_state_start: u32; - static __bootloader_state_end: u32; - static __bootloader_dfu_start: u32; - static __bootloader_dfu_end: u32; - } - - let dfu = unsafe { - Partition::new( - &__bootloader_dfu_start as *const u32 as u32, - &__bootloader_dfu_end as *const u32 as u32, - ) - }; - let state = unsafe { - Partition::new( - &__bootloader_state_start as *const u32 as u32, - &__bootloader_state_end as *const u32 as u32, - ) - }; - - trace!("DFU: 0x{:x} - 0x{:x}", dfu.from, dfu.to); - trace!("STATE: 0x{:x} - 0x{:x}", state.from, state.to); - FirmwareUpdater::new(dfu, state) - } -} diff --git a/embassy-boot/boot/src/lib.rs b/embassy-boot/boot/src/lib.rs index 4521fecb..af2d3ce0 100644 --- a/embassy-boot/boot/src/lib.rs +++ b/embassy-boot/boot/src/lib.rs @@ -11,8 +11,10 @@ mod mem_flash; mod partition; pub use boot_loader::{BootError, BootFlash, BootLoader, FlashConfig, MultiFlashConfig, SingleFlashConfig}; -pub use firmware_updater::{FirmwareUpdater, FirmwareUpdaterError}; pub use partition::Partition; +#[cfg(feature = "nightly")] +pub use firmware_updater::FirmwareUpdater; +pub use firmware_updater::{BlockingFirmwareUpdater, FirmwareUpdaterConfig, FirmwareUpdaterError}; pub(crate) const BOOT_MAGIC: u8 = 0xD0; pub(crate) const SWAP_MAGIC: u8 = 0xF0; From c5ec453ec1e87c2f5c5047bf357ae99298aa6d12 Mon Sep 17 00:00:00 2001 From: Rasmus Melchior Jacobsen Date: Tue, 30 May 2023 13:38:00 +0200 Subject: [PATCH 160/178] Add bootloader helper for creating config from linkerfile symbols --- embassy-boot/boot/src/boot_loader.rs | 136 ++++++++++++++------------- embassy-boot/boot/src/lib.rs | 2 +- 2 files changed, 72 insertions(+), 66 deletions(-) diff --git a/embassy-boot/boot/src/boot_loader.rs b/embassy-boot/boot/src/boot_loader.rs index 50eb5e66..bdf7bd7f 100644 --- a/embassy-boot/boot/src/boot_loader.rs +++ b/embassy-boot/boot/src/boot_loader.rs @@ -1,8 +1,11 @@ -use embedded_storage::nor_flash::{ErrorType, NorFlash, NorFlashError, NorFlashErrorKind, ReadNorFlash}; +use core::cell::RefCell; -use crate::{State, BOOT_MAGIC, SWAP_MAGIC}; +use embassy_embedded_hal::flash::partition::BlockingPartition; +use embassy_sync::blocking_mutex::raw::NoopRawMutex; +use embassy_sync::blocking_mutex::Mutex; +use embedded_storage::nor_flash::{NorFlash, NorFlashError, NorFlashErrorKind}; -const STATE_ERASE_VALUE: u8 = 0xFF; +use crate::{State, BOOT_MAGIC, STATE_ERASE_VALUE, SWAP_MAGIC}; /// Errors returned by bootloader #[derive(PartialEq, Eq, Debug)] @@ -32,14 +35,69 @@ where } } +/// Bootloader flash configuration holding the three flashes used by the bootloader +/// +/// If only a single flash is actually used, then that flash should be partitioned into three partitions before use. +/// The easiest way to do this is to use [`BootLoaderConfig::from_linkerfile_blocking`] which will partition +/// the provided flash according to symbols defined in the linkerfile. +pub struct BootLoaderConfig { + /// Flash type used for the active partition - the partition which will be booted from. + pub active: ACTIVE, + /// Flash type used for the dfu partition - the partition which will be swapped in when requested. + pub dfu: DFU, + /// Flash type used for the state partition. + pub state: STATE, +} + +impl<'a, FLASH: NorFlash> + BootLoaderConfig< + BlockingPartition<'a, NoopRawMutex, FLASH>, + BlockingPartition<'a, NoopRawMutex, FLASH>, + BlockingPartition<'a, NoopRawMutex, FLASH>, + > +{ + /// Create a bootloader config from the flash and address symbols defined in the linkerfile + // #[cfg(target_os = "none")] + pub fn from_linkerfile_blocking(flash: &'a Mutex>) -> Self { + extern "C" { + static __bootloader_state_start: u32; + static __bootloader_state_end: u32; + static __bootloader_active_start: u32; + static __bootloader_active_end: u32; + static __bootloader_dfu_start: u32; + static __bootloader_dfu_end: u32; + } + + let active = unsafe { + let start = &__bootloader_active_start as *const u32 as u32; + let end = &__bootloader_active_end as *const u32 as u32; + trace!("ACTIVE: 0x{:x} - 0x{:x}", start, end); + + BlockingPartition::new(flash, start, end - start) + }; + let dfu = unsafe { + let start = &__bootloader_dfu_start as *const u32 as u32; + let end = &__bootloader_dfu_end as *const u32 as u32; + trace!("DFU: 0x{:x} - 0x{:x}", start, end); + + BlockingPartition::new(flash, start, end - start) + }; + let state = unsafe { + let start = &__bootloader_state_start as *const u32 as u32; + let end = &__bootloader_state_end as *const u32 as u32; + trace!("STATE: 0x{:x} - 0x{:x}", start, end); + + BlockingPartition::new(flash, start, end - start) + }; + + Self { active, dfu, state } + } +} + /// BootLoader works with any flash implementing embedded_storage. pub struct BootLoader { - /// Flash type used for the active partition - the partition which will be booted from. active: ACTIVE, - /// Flash type used for the dfu partition - he partition which will be swapped in when requested. dfu: DFU, - /// Flash type used for the state partition. - /// /// The state partition has the following format: /// All ranges are in multiples of WRITE_SIZE bytes. /// | Range | Description | @@ -61,8 +119,12 @@ impl BootLoader Self { - Self { active, dfu, state } + pub fn new(config: BootLoaderConfig) -> Self { + Self { + active: config.active, + dfu: config.dfu, + state: config.state, + } } /// Perform necessary boot preparations like swapping images. @@ -340,62 +402,6 @@ fn assert_partitions( assert!(2 + 2 * (active.capacity() as u32 / page_size) <= state.capacity() as u32 / STATE::WRITE_SIZE as u32); } -/// A flash wrapper implementing the Flash and embedded_storage traits. -pub struct BootFlash -where - F: NorFlash, -{ - flash: F, -} - -impl BootFlash -where - F: NorFlash, -{ - /// Create a new instance of a bootable flash - pub fn new(flash: F) -> Self { - Self { flash } - } -} - -impl ErrorType for BootFlash -where - F: NorFlash, -{ - type Error = F::Error; -} - -impl NorFlash for BootFlash -where - F: NorFlash, -{ - const WRITE_SIZE: usize = F::WRITE_SIZE; - const ERASE_SIZE: usize = F::ERASE_SIZE; - - fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> { - F::erase(&mut self.flash, from, to) - } - - fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> { - F::write(&mut self.flash, offset, bytes) - } -} - -impl ReadNorFlash for BootFlash -where - F: NorFlash, -{ - const READ_SIZE: usize = F::READ_SIZE; - - fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> { - F::read(&mut self.flash, offset, bytes) - } - - fn capacity(&self) -> usize { - F::capacity(&self.flash) - } -} - #[cfg(test)] mod tests { use super::*; diff --git a/embassy-boot/boot/src/lib.rs b/embassy-boot/boot/src/lib.rs index af2d3ce0..f2034fa8 100644 --- a/embassy-boot/boot/src/lib.rs +++ b/embassy-boot/boot/src/lib.rs @@ -10,8 +10,8 @@ mod firmware_updater; mod mem_flash; mod partition; -pub use boot_loader::{BootError, BootFlash, BootLoader, FlashConfig, MultiFlashConfig, SingleFlashConfig}; pub use partition::Partition; +pub use boot_loader::{BootError, BootLoader, BootLoaderConfig}; #[cfg(feature = "nightly")] pub use firmware_updater::FirmwareUpdater; pub use firmware_updater::{BlockingFirmwareUpdater, FirmwareUpdaterConfig, FirmwareUpdaterError}; From 1cd87f0028c8a0f9b87a09016a22179fb05ced33 Mon Sep 17 00:00:00 2001 From: Rasmus Melchior Jacobsen Date: Tue, 30 May 2023 13:40:04 +0200 Subject: [PATCH 161/178] Cleanup MemFlash --- embassy-boot/boot/src/mem_flash.rs | 108 +++++++++++++++-------------- 1 file changed, 57 insertions(+), 51 deletions(-) diff --git a/embassy-boot/boot/src/mem_flash.rs b/embassy-boot/boot/src/mem_flash.rs index 3ba84a92..2728e972 100644 --- a/embassy-boot/boot/src/mem_flash.rs +++ b/embassy-boot/boot/src/mem_flash.rs @@ -34,6 +34,52 @@ impl MemFla } } + fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), MemFlashError> { + let len = bytes.len(); + bytes.copy_from_slice(&self.mem[offset as usize..offset as usize + len]); + Ok(()) + } + + fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), MemFlashError> { + let offset = offset as usize; + assert!(bytes.len() % WRITE_SIZE == 0); + assert!(offset % WRITE_SIZE == 0); + assert!(offset + bytes.len() <= SIZE); + + if let Some(pending_successes) = self.pending_write_successes { + if pending_successes > 0 { + self.pending_write_successes = Some(pending_successes - 1); + } else { + return Err(MemFlashError); + } + } + + for ((offset, mem_byte), new_byte) in self + .mem + .iter_mut() + .enumerate() + .skip(offset) + .take(bytes.len()) + .zip(bytes) + { + assert_eq!(0xFF, *mem_byte, "Offset {} is not erased", offset); + *mem_byte = *new_byte; + } + + Ok(()) + } + + fn erase(&mut self, from: u32, to: u32) -> Result<(), MemFlashError> { + let from = from as usize; + let to = to as usize; + assert!(from % ERASE_SIZE == 0); + assert!(to % ERASE_SIZE == 0, "To: {}, erase size: {}", to, ERASE_SIZE); + for i in from..to { + self.mem[i] = 0xFF; + } + Ok(()) + } + pub fn program(&mut self, offset: u32, bytes: &[u8]) -> Result<(), MemFlashError> { let offset = offset as usize; assert!(bytes.len() % WRITE_SIZE == 0); @@ -44,12 +90,6 @@ impl MemFla Ok(()) } - - pub fn assert_eq(&self, offset: u32, expectation: &[u8]) { - for i in 0..expectation.len() { - assert_eq!(self.mem[offset as usize + i], expectation[i], "Index {}", i); - } - } } impl Default @@ -78,9 +118,7 @@ impl ReadNo const READ_SIZE: usize = 1; fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> { - let len = bytes.len(); - bytes.copy_from_slice(&self.mem[offset as usize..offset as usize + len]); - Ok(()) + self.read(offset, bytes) } fn capacity(&self) -> usize { @@ -94,44 +132,12 @@ impl NorFla const WRITE_SIZE: usize = WRITE_SIZE; const ERASE_SIZE: usize = ERASE_SIZE; - fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> { - let from = from as usize; - let to = to as usize; - assert!(from % ERASE_SIZE == 0); - assert!(to % ERASE_SIZE == 0, "To: {}, erase size: {}", to, ERASE_SIZE); - for i in from..to { - self.mem[i] = 0xFF; - } - Ok(()) + fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> { + self.write(offset, bytes) } - fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> { - let offset = offset as usize; - assert!(bytes.len() % WRITE_SIZE == 0); - assert!(offset % WRITE_SIZE == 0); - assert!(offset + bytes.len() <= SIZE); - - if let Some(pending_successes) = self.pending_write_successes { - if pending_successes > 0 { - self.pending_write_successes = Some(pending_successes - 1); - } else { - return Err(MemFlashError); - } - } - - for ((offset, mem_byte), new_byte) in self - .mem - .iter_mut() - .enumerate() - .skip(offset) - .take(bytes.len()) - .zip(bytes) - { - assert_eq!(0xFF, *mem_byte, "Offset {} is not erased", offset); - *mem_byte = *new_byte; - } - - Ok(()) + fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> { + self.erase(from, to) } } @@ -142,11 +148,11 @@ impl AsyncR const READ_SIZE: usize = 1; async fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> { - ::read(self, offset, bytes) + self.read(offset, bytes) } fn capacity(&self) -> usize { - ::capacity(self) + SIZE } } @@ -157,11 +163,11 @@ impl AsyncN const WRITE_SIZE: usize = WRITE_SIZE; const ERASE_SIZE: usize = ERASE_SIZE; - async fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> { - ::erase(self, from, to) + async fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> { + self.write(offset, bytes) } - async fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> { - ::write(self, offset, bytes) + async fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> { + self.erase(from, to) } } From b23e40f72242a9f4759d8a6d4a924c8c9d041c67 Mon Sep 17 00:00:00 2001 From: Rasmus Melchior Jacobsen Date: Tue, 30 May 2023 13:41:10 +0200 Subject: [PATCH 162/178] Add TestFlash helper --- embassy-boot/boot/src/lib.rs | 6 ++ embassy-boot/boot/src/test_flash/asynch.rs | 57 +++++++++++++++++ embassy-boot/boot/src/test_flash/blocking.rs | 65 ++++++++++++++++++++ embassy-boot/boot/src/test_flash/mod.rs | 7 +++ 4 files changed, 135 insertions(+) create mode 100644 embassy-boot/boot/src/test_flash/asynch.rs create mode 100644 embassy-boot/boot/src/test_flash/blocking.rs create mode 100644 embassy-boot/boot/src/test_flash/mod.rs diff --git a/embassy-boot/boot/src/lib.rs b/embassy-boot/boot/src/lib.rs index f2034fa8..c76087ff 100644 --- a/embassy-boot/boot/src/lib.rs +++ b/embassy-boot/boot/src/lib.rs @@ -7,10 +7,16 @@ mod fmt; mod boot_loader; mod digest_adapters; mod firmware_updater; +#[cfg(test)] mod mem_flash; mod partition; +#[cfg(test)] +mod test_flash; pub use partition::Partition; +// The expected value of the flash after an erase +// TODO: Use the value provided by NorFlash when available +pub(crate) const STATE_ERASE_VALUE: u8 = 0xFF; pub use boot_loader::{BootError, BootLoader, BootLoaderConfig}; #[cfg(feature = "nightly")] pub use firmware_updater::FirmwareUpdater; diff --git a/embassy-boot/boot/src/test_flash/asynch.rs b/embassy-boot/boot/src/test_flash/asynch.rs new file mode 100644 index 00000000..b5b3c253 --- /dev/null +++ b/embassy-boot/boot/src/test_flash/asynch.rs @@ -0,0 +1,57 @@ +use embassy_embedded_hal::flash::partition::Partition; +use embassy_sync::blocking_mutex::raw::NoopRawMutex; +use embassy_sync::mutex::Mutex; +use embedded_storage_async::nor_flash::NorFlash; + +pub struct AsyncTestFlash +where + ACTIVE: NorFlash, + DFU: NorFlash, + STATE: NorFlash, +{ + active: Mutex, + dfu: Mutex, + state: Mutex, +} + +impl AsyncTestFlash +where + ACTIVE: NorFlash, + DFU: NorFlash, + STATE: NorFlash, +{ + pub fn new(active: ACTIVE, dfu: DFU, state: STATE) -> Self { + Self { + active: Mutex::new(active), + dfu: Mutex::new(dfu), + state: Mutex::new(state), + } + } + + pub fn active(&self) -> Partition { + Self::create_partition(&self.active) + } + + pub fn dfu(&self) -> Partition { + Self::create_partition(&self.dfu) + } + + pub fn state(&self) -> Partition { + Self::create_partition(&self.state) + } + + fn create_partition(mutex: &Mutex) -> Partition { + Partition::new(mutex, 0, mutex.try_lock().unwrap().capacity() as u32) + } +} + +impl AsyncTestFlash +where + ACTIVE: NorFlash + embedded_storage::nor_flash::NorFlash, + DFU: NorFlash + embedded_storage::nor_flash::NorFlash, + STATE: NorFlash + embedded_storage::nor_flash::NorFlash, +{ + pub fn into_blocking(self) -> super::BlockingTestFlash { + super::BlockingTestFlash::new(self.active.into_inner(), self.dfu.into_inner(), self.state.into_inner()) + } +} diff --git a/embassy-boot/boot/src/test_flash/blocking.rs b/embassy-boot/boot/src/test_flash/blocking.rs new file mode 100644 index 00000000..77876a21 --- /dev/null +++ b/embassy-boot/boot/src/test_flash/blocking.rs @@ -0,0 +1,65 @@ +use core::cell::RefCell; + +use embassy_embedded_hal::flash::partition::BlockingPartition; +use embassy_sync::blocking_mutex::raw::NoopRawMutex; +use embassy_sync::blocking_mutex::Mutex; +use embedded_storage::nor_flash::NorFlash; + +pub struct BlockingTestFlash +where + ACTIVE: NorFlash, + DFU: NorFlash, + STATE: NorFlash, +{ + active: Mutex>, + dfu: Mutex>, + state: Mutex>, +} + +impl BlockingTestFlash +where + ACTIVE: NorFlash, + DFU: NorFlash, + STATE: NorFlash, +{ + pub fn new(active: ACTIVE, dfu: DFU, state: STATE) -> Self { + Self { + active: Mutex::new(RefCell::new(active)), + dfu: Mutex::new(RefCell::new(dfu)), + state: Mutex::new(RefCell::new(state)), + } + } + + pub fn active(&self) -> BlockingPartition { + Self::create_partition(&self.active) + } + + pub fn dfu(&self) -> BlockingPartition { + Self::create_partition(&self.dfu) + } + + pub fn state(&self) -> BlockingPartition { + Self::create_partition(&self.state) + } + + pub fn create_partition( + mutex: &Mutex>, + ) -> BlockingPartition { + BlockingPartition::new(mutex, 0, mutex.lock(|f| f.borrow().capacity()) as u32) + } +} + +impl BlockingTestFlash +where + ACTIVE: NorFlash + embedded_storage_async::nor_flash::NorFlash, + DFU: NorFlash + embedded_storage_async::nor_flash::NorFlash, + STATE: NorFlash + embedded_storage_async::nor_flash::NorFlash, +{ + pub fn into_async(self) -> super::AsyncTestFlash { + super::AsyncTestFlash::new( + self.active.into_inner().into_inner(), + self.dfu.into_inner().into_inner(), + self.state.into_inner().into_inner(), + ) + } +} diff --git a/embassy-boot/boot/src/test_flash/mod.rs b/embassy-boot/boot/src/test_flash/mod.rs new file mode 100644 index 00000000..a0672322 --- /dev/null +++ b/embassy-boot/boot/src/test_flash/mod.rs @@ -0,0 +1,7 @@ +#[cfg(feature = "nightly")] +mod asynch; +mod blocking; + +#[cfg(feature = "nightly")] +pub(crate) use asynch::AsyncTestFlash; +pub(crate) use blocking::BlockingTestFlash; From 551f76c70067bfa14b159a198cdb92f810f40607 Mon Sep 17 00:00:00 2001 From: Rasmus Melchior Jacobsen Date: Tue, 30 May 2023 13:44:12 +0200 Subject: [PATCH 163/178] Remove legacy Partition type and use the one from embedded-hal --- embassy-boot/boot/Cargo.toml | 7 +- embassy-boot/boot/src/lib.rs | 2 - embassy-boot/boot/src/partition.rs | 144 ----------------------------- 3 files changed, 4 insertions(+), 149 deletions(-) delete mode 100644 embassy-boot/boot/src/partition.rs diff --git a/embassy-boot/boot/Cargo.toml b/embassy-boot/boot/Cargo.toml index 3fdf69c5..415d7960 100644 --- a/embassy-boot/boot/Cargo.toml +++ b/embassy-boot/boot/Cargo.toml @@ -27,9 +27,10 @@ defmt = { version = "0.3", optional = true } digest = "0.10" log = { version = "0.4", optional = true } ed25519-dalek = { version = "1.0.1", default_features = false, features = ["u32_backend"], optional = true } +embassy-embedded-hal = { version = "0.1.0", path = "../../embassy-embedded-hal" } embassy-sync = { version = "0.2.0", path = "../../embassy-sync" } embedded-storage = "0.3.0" -embedded-storage-async = { version = "0.4.0", optional = true} +embedded-storage-async = { version = "0.4.0", optional = true } salty = { git = "https://github.com/ycrypto/salty.git", rev = "a9f17911a5024698406b75c0fac56ab5ccf6a8c7", optional = true } signature = { version = "1.6.4", default-features = false } @@ -39,7 +40,7 @@ env_logger = "0.9" rand = "0.7" # ed25519-dalek v1.0.1 depends on this exact version futures = { version = "0.3", features = ["executor"] } sha1 = "0.10.5" -embassy-embedded-hal = { version = "0.1.0", path = "../../embassy-embedded-hal" } +critical-section = { version = "1.1.1", features = ["std"] } [dev-dependencies.ed25519-dalek] default_features = false @@ -49,7 +50,7 @@ features = ["rand", "std", "u32_backend"] ed25519-dalek = ["dep:ed25519-dalek", "_verify"] ed25519-salty = ["dep:salty", "_verify"] -nightly = ["dep:embedded-storage-async"] +nightly = ["dep:embedded-storage-async", "embassy-embedded-hal/nightly"] #Internal features _verify = [] diff --git a/embassy-boot/boot/src/lib.rs b/embassy-boot/boot/src/lib.rs index c76087ff..15d3a4f2 100644 --- a/embassy-boot/boot/src/lib.rs +++ b/embassy-boot/boot/src/lib.rs @@ -9,11 +9,9 @@ mod digest_adapters; mod firmware_updater; #[cfg(test)] mod mem_flash; -mod partition; #[cfg(test)] mod test_flash; -pub use partition::Partition; // The expected value of the flash after an erase // TODO: Use the value provided by NorFlash when available pub(crate) const STATE_ERASE_VALUE: u8 = 0xFF; diff --git a/embassy-boot/boot/src/partition.rs b/embassy-boot/boot/src/partition.rs deleted file mode 100644 index 7b56a824..00000000 --- a/embassy-boot/boot/src/partition.rs +++ /dev/null @@ -1,144 +0,0 @@ -use embedded_storage::nor_flash::{NorFlash, ReadNorFlash}; -#[cfg(feature = "nightly")] -use embedded_storage_async::nor_flash::{NorFlash as AsyncNorFlash, ReadNorFlash as AsyncReadNorFlash}; - -/// A region in flash used by the bootloader. -#[derive(Copy, Clone, Debug)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct Partition { - /// The offset into the flash where the partition starts. - pub from: u32, - /// The offset into the flash where the partition ends. - pub to: u32, -} - -impl Partition { - /// Create a new partition with the provided range - pub const fn new(from: u32, to: u32) -> Self { - Self { from, to } - } - - /// Return the size of the partition - pub const fn size(&self) -> u32 { - self.to - self.from - } - - /// Read from the partition on the provided flash - #[cfg(feature = "nightly")] - pub async fn read( - &self, - flash: &mut F, - offset: u32, - bytes: &mut [u8], - ) -> Result<(), F::Error> { - let offset = self.from as u32 + offset; - flash.read(offset, bytes).await - } - - /// Write to the partition on the provided flash - #[cfg(feature = "nightly")] - pub async fn write(&self, flash: &mut F, offset: u32, bytes: &[u8]) -> Result<(), F::Error> { - let offset = self.from as u32 + offset; - flash.write(offset, bytes).await?; - trace!("Wrote from 0x{:x} len {}", offset, bytes.len()); - Ok(()) - } - - /// Erase part of the partition on the provided flash - #[cfg(feature = "nightly")] - pub async fn erase(&self, flash: &mut F, from: u32, to: u32) -> Result<(), F::Error> { - let from = self.from as u32 + from; - let to = self.from as u32 + to; - flash.erase(from, to).await?; - trace!("Erased from 0x{:x} to 0x{:x}", from, to); - Ok(()) - } - - /// Erase the entire partition - #[cfg(feature = "nightly")] - pub(crate) async fn wipe(&self, flash: &mut F) -> Result<(), F::Error> { - let from = self.from as u32; - let to = self.to as u32; - flash.erase(from, to).await?; - trace!("Wiped from 0x{:x} to 0x{:x}", from, to); - Ok(()) - } - - /// Read from the partition on the provided flash - pub fn read_blocking(&self, flash: &mut F, offset: u32, bytes: &mut [u8]) -> Result<(), F::Error> { - let offset = self.from as u32 + offset; - flash.read(offset, bytes) - } - - /// Write to the partition on the provided flash - pub fn write_blocking(&self, flash: &mut F, offset: u32, bytes: &[u8]) -> Result<(), F::Error> { - let offset = self.from as u32 + offset; - flash.write(offset, bytes)?; - trace!("Wrote from 0x{:x} len {}", offset, bytes.len()); - Ok(()) - } - - /// Erase part of the partition on the provided flash - pub fn erase_blocking(&self, flash: &mut F, from: u32, to: u32) -> Result<(), F::Error> { - let from = self.from as u32 + from; - let to = self.from as u32 + to; - flash.erase(from, to)?; - trace!("Erased from 0x{:x} to 0x{:x}", from, to); - Ok(()) - } - - /// Erase the entire partition - pub(crate) fn wipe_blocking(&self, flash: &mut F) -> Result<(), F::Error> { - let from = self.from as u32; - let to = self.to as u32; - flash.erase(from, to)?; - trace!("Wiped from 0x{:x} to 0x{:x}", from, to); - Ok(()) - } -} - -#[cfg(test)] -mod tests { - use crate::mem_flash::MemFlash; - use crate::Partition; - - #[test] - fn can_erase() { - let mut flash = MemFlash::<1024, 64, 4>::new(0x00); - let partition = Partition::new(256, 512); - - partition.erase_blocking(&mut flash, 64, 192).unwrap(); - - for (index, byte) in flash.mem.iter().copied().enumerate().take(256 + 64) { - assert_eq!(0x00, byte, "Index {}", index); - } - - for (index, byte) in flash.mem.iter().copied().enumerate().skip(256 + 64).take(128) { - assert_eq!(0xFF, byte, "Index {}", index); - } - - for (index, byte) in flash.mem.iter().copied().enumerate().skip(256 + 64 + 128) { - assert_eq!(0x00, byte, "Index {}", index); - } - } - - #[test] - fn can_wipe() { - let mut flash = MemFlash::<1024, 64, 4>::new(0x00); - let partition = Partition::new(256, 512); - - partition.wipe_blocking(&mut flash).unwrap(); - - for (index, byte) in flash.mem.iter().copied().enumerate().take(256) { - assert_eq!(0x00, byte, "Index {}", index); - } - - for (index, byte) in flash.mem.iter().copied().enumerate().skip(256).take(256) { - assert_eq!(0xFF, byte, "Index {}", index); - } - - for (index, byte) in flash.mem.iter().copied().enumerate().skip(512) { - assert_eq!(0x00, byte, "Index {}", index); - } - } -} From c6a984f506530bc08464800abc332be9c49ac198 Mon Sep 17 00:00:00 2001 From: Rasmus Melchior Jacobsen Date: Tue, 30 May 2023 13:55:49 +0200 Subject: [PATCH 164/178] Align tests --- embassy-boot/boot/src/boot_loader.rs | 2 +- embassy-boot/boot/src/lib.rs | 238 +++++++++++-------- embassy-boot/boot/src/test_flash/asynch.rs | 17 +- embassy-boot/boot/src/test_flash/blocking.rs | 22 +- 4 files changed, 162 insertions(+), 117 deletions(-) diff --git a/embassy-boot/boot/src/boot_loader.rs b/embassy-boot/boot/src/boot_loader.rs index bdf7bd7f..a8c19197 100644 --- a/embassy-boot/boot/src/boot_loader.rs +++ b/embassy-boot/boot/src/boot_loader.rs @@ -40,7 +40,7 @@ where /// If only a single flash is actually used, then that flash should be partitioned into three partitions before use. /// The easiest way to do this is to use [`BootLoaderConfig::from_linkerfile_blocking`] which will partition /// the provided flash according to symbols defined in the linkerfile. -pub struct BootLoaderConfig { +pub struct BootLoaderConfig { /// Flash type used for the active partition - the partition which will be booted from. pub active: ACTIVE, /// Flash type used for the dfu partition - the partition which will be swapped in when requested. diff --git a/embassy-boot/boot/src/lib.rs b/embassy-boot/boot/src/lib.rs index 15d3a4f2..d13eafdd 100644 --- a/embassy-boot/boot/src/lib.rs +++ b/embassy-boot/boot/src/lib.rs @@ -51,10 +51,18 @@ impl AsMut<[u8]> for AlignedBuffer { #[cfg(test)] mod tests { + use embedded_storage::nor_flash::{NorFlash, ReadNorFlash}; + #[cfg(feature = "nightly")] + use embedded_storage_async::nor_flash::NorFlash as AsyncNorFlash; use futures::executor::block_on; use super::*; + use crate::boot_loader::BootLoaderConfig; + use crate::firmware_updater::FirmwareUpdaterConfig; use crate::mem_flash::MemFlash; + #[cfg(feature = "nightly")] + use crate::test_flash::AsyncTestFlash; + use crate::test_flash::BlockingTestFlash; /* #[test] @@ -73,147 +81,173 @@ mod tests { #[test] fn test_boot_state() { - const STATE: Partition = Partition::new(0, 4096); - const ACTIVE: Partition = Partition::new(4096, 61440); - const DFU: Partition = Partition::new(61440, 122880); + let flash = BlockingTestFlash::new(BootLoaderConfig { + active: MemFlash::<57344, 4096, 4>::default(), + dfu: MemFlash::<61440, 4096, 4>::default(), + state: MemFlash::<4096, 4096, 4>::default(), + }); - let mut flash = MemFlash::<131072, 4096, 4>::default(); - flash.mem[0..4].copy_from_slice(&[BOOT_MAGIC; 4]); - let mut flash = SingleFlashConfig::new(&mut flash); + flash.state().write(0, &[BOOT_MAGIC; 4]).unwrap(); - let mut bootloader: BootLoader = BootLoader::new(ACTIVE, DFU, STATE); + let mut bootloader = BootLoader::new(BootLoaderConfig { + active: flash.active(), + dfu: flash.dfu(), + state: flash.state(), + }); let mut page = [0; 4096]; - assert_eq!(State::Boot, bootloader.prepare_boot(&mut flash, &mut page).unwrap()); + assert_eq!(State::Boot, bootloader.prepare_boot(&mut page).unwrap()); } #[test] #[cfg(all(feature = "nightly", not(feature = "_verify")))] fn test_swap_state() { - const STATE: Partition = Partition::new(0, 4096); - const ACTIVE: Partition = Partition::new(4096, 61440); - const DFU: Partition = Partition::new(61440, 122880); - let mut flash = MemFlash::<131072, 4096, 4>::random(); + const FIRMWARE_SIZE: usize = 57344; + let flash = AsyncTestFlash::new(BootLoaderConfig { + active: MemFlash::::default(), + dfu: MemFlash::<61440, 4096, 4>::default(), + state: MemFlash::<4096, 4096, 4>::default(), + }); - let original = [rand::random::(); ACTIVE.size() as usize]; - let update = [rand::random::(); ACTIVE.size() as usize]; + const ORIGINAL: [u8; FIRMWARE_SIZE] = [0x55; FIRMWARE_SIZE]; + const UPDATE: [u8; FIRMWARE_SIZE] = [0xAA; FIRMWARE_SIZE]; let mut aligned = [0; 4]; - flash.program(ACTIVE.from, &original).unwrap(); + block_on(flash.active().erase(0, ORIGINAL.len() as u32)).unwrap(); + block_on(flash.active().write(0, &ORIGINAL)).unwrap(); - let mut bootloader: BootLoader = BootLoader::new(ACTIVE, DFU, STATE); - let mut updater = FirmwareUpdater::new(DFU, STATE); - block_on(updater.write_firmware(0, &update, &mut flash)).unwrap(); - block_on(updater.mark_updated(&mut flash, &mut aligned)).unwrap(); + let mut updater = FirmwareUpdater::new(FirmwareUpdaterConfig { + dfu: flash.dfu(), + state: flash.state(), + }); + block_on(updater.write_firmware(0, &UPDATE)).unwrap(); + block_on(updater.mark_updated(&mut aligned)).unwrap(); + + let flash = flash.into_blocking(); + let mut bootloader = BootLoader::new(BootLoaderConfig { + active: flash.active(), + dfu: flash.dfu(), + state: flash.state(), + }); let mut page = [0; 1024]; - assert_eq!( - State::Swap, - bootloader - .prepare_boot(&mut SingleFlashConfig::new(&mut flash), &mut page) - .unwrap() - ); + assert_eq!(State::Swap, bootloader.prepare_boot(&mut page).unwrap()); - flash.assert_eq(ACTIVE.from, &update); + let mut read_buf = [0; FIRMWARE_SIZE]; + flash.active().read(0, &mut read_buf).unwrap(); + assert_eq!(UPDATE, read_buf); // First DFU page is untouched - flash.assert_eq(DFU.from + 4096, &original); + flash.dfu().read(4096, &mut read_buf).unwrap(); + assert_eq!(ORIGINAL, read_buf); // Running again should cause a revert - assert_eq!( - State::Swap, - bootloader - .prepare_boot(&mut SingleFlashConfig::new(&mut flash), &mut page) - .unwrap() - ); + assert_eq!(State::Swap, bootloader.prepare_boot(&mut page).unwrap()); - flash.assert_eq(ACTIVE.from, &original); - // Last page is untouched - flash.assert_eq(DFU.from, &update); + let mut read_buf = [0; FIRMWARE_SIZE]; + flash.active().read(0, &mut read_buf).unwrap(); + assert_eq!(ORIGINAL, read_buf); + // Last DFU page is untouched + flash.dfu().read(0, &mut read_buf).unwrap(); + assert_eq!(UPDATE, read_buf); // Mark as booted - block_on(updater.mark_booted(&mut flash, &mut aligned)).unwrap(); - assert_eq!( - State::Boot, - bootloader - .prepare_boot(&mut SingleFlashConfig::new(&mut flash), &mut page) - .unwrap() - ); + let flash = flash.into_async(); + let mut updater = FirmwareUpdater::new(FirmwareUpdaterConfig { + dfu: flash.dfu(), + state: flash.state(), + }); + block_on(updater.mark_booted(&mut aligned)).unwrap(); + + let flash = flash.into_blocking(); + let mut bootloader = BootLoader::new(BootLoaderConfig { + active: flash.active(), + dfu: flash.dfu(), + state: flash.state(), + }); + assert_eq!(State::Boot, bootloader.prepare_boot(&mut page).unwrap()); } #[test] #[cfg(all(feature = "nightly", not(feature = "_verify")))] - fn test_separate_flash_active_page_biggest() { - const STATE: Partition = Partition::new(2048, 4096); - const ACTIVE: Partition = Partition::new(4096, 16384); - const DFU: Partition = Partition::new(0, 16384); + fn test_swap_state_active_page_biggest() { + const FIRMWARE_SIZE: usize = 12288; + let flash = AsyncTestFlash::new(BootLoaderConfig { + active: MemFlash::<12288, 4096, 8>::random(), + dfu: MemFlash::<16384, 2048, 8>::random(), + state: MemFlash::<2048, 128, 4>::random(), + }); - let mut active = MemFlash::<16384, 4096, 8>::random(); - let mut dfu = MemFlash::<16384, 2048, 8>::random(); - let mut state = MemFlash::<4096, 128, 4>::random(); + const ORIGINAL: [u8; FIRMWARE_SIZE] = [0x55; FIRMWARE_SIZE]; + const UPDATE: [u8; FIRMWARE_SIZE] = [0xAA; FIRMWARE_SIZE]; let mut aligned = [0; 4]; - let original = [rand::random::(); ACTIVE.size() as usize]; - let update = [rand::random::(); ACTIVE.size() as usize]; + block_on(flash.active().erase(0, ORIGINAL.len() as u32)).unwrap(); + block_on(flash.active().write(0, &ORIGINAL)).unwrap(); - active.program(ACTIVE.from, &original).unwrap(); + let mut updater = FirmwareUpdater::new(FirmwareUpdaterConfig { + dfu: flash.dfu(), + state: flash.state(), + }); + block_on(updater.write_firmware(0, &UPDATE)).unwrap(); + block_on(updater.mark_updated(&mut aligned)).unwrap(); - let mut updater = FirmwareUpdater::new(DFU, STATE); + let flash = flash.into_blocking(); + let mut bootloader = BootLoader::new(BootLoaderConfig { + active: flash.active(), + dfu: flash.dfu(), + state: flash.state(), + }); - block_on(updater.write_firmware(0, &update, &mut dfu)).unwrap(); - block_on(updater.mark_updated(&mut state, &mut aligned)).unwrap(); - - let mut bootloader: BootLoader = BootLoader::new(ACTIVE, DFU, STATE); let mut page = [0; 4096]; + assert_eq!(State::Swap, bootloader.prepare_boot(&mut page).unwrap()); - assert_eq!( - State::Swap, - bootloader - .prepare_boot(&mut MultiFlashConfig::new(&mut active, &mut state, &mut dfu), &mut page) - .unwrap() - ); - - active.assert_eq(ACTIVE.from, &update); + let mut read_buf = [0; FIRMWARE_SIZE]; + flash.active().read(0, &mut read_buf).unwrap(); + assert_eq!(UPDATE, read_buf); // First DFU page is untouched - dfu.assert_eq(DFU.from + 4096, &original); + flash.dfu().read(4096, &mut read_buf).unwrap(); + assert_eq!(ORIGINAL, read_buf); } #[test] #[cfg(all(feature = "nightly", not(feature = "_verify")))] - fn test_separate_flash_dfu_page_biggest() { - const STATE: Partition = Partition::new(2048, 4096); - const ACTIVE: Partition = Partition::new(4096, 16384); - const DFU: Partition = Partition::new(0, 16384); + fn test_swap_state_dfu_page_biggest() { + const FIRMWARE_SIZE: usize = 12288; + let flash = AsyncTestFlash::new(BootLoaderConfig { + active: MemFlash::::random(), + dfu: MemFlash::<16384, 4096, 8>::random(), + state: MemFlash::<2048, 128, 4>::random(), + }); + const ORIGINAL: [u8; FIRMWARE_SIZE] = [0x55; FIRMWARE_SIZE]; + const UPDATE: [u8; FIRMWARE_SIZE] = [0xAA; FIRMWARE_SIZE]; let mut aligned = [0; 4]; - let mut active = MemFlash::<16384, 2048, 4>::random(); - let mut dfu = MemFlash::<16384, 4096, 8>::random(); - let mut state = MemFlash::<4096, 128, 4>::random(); - let original = [rand::random::(); ACTIVE.size() as usize]; - let update = [rand::random::(); ACTIVE.size() as usize]; + block_on(flash.active().erase(0, ORIGINAL.len() as u32)).unwrap(); + block_on(flash.active().write(0, &ORIGINAL)).unwrap(); - active.program(ACTIVE.from, &original).unwrap(); + let mut updater = FirmwareUpdater::new(FirmwareUpdaterConfig { + dfu: flash.dfu(), + state: flash.state(), + }); + block_on(updater.write_firmware(0, &UPDATE)).unwrap(); + block_on(updater.mark_updated(&mut aligned)).unwrap(); - let mut updater = FirmwareUpdater::new(DFU, STATE); - - block_on(updater.write_firmware(0, &update, &mut dfu)).unwrap(); - block_on(updater.mark_updated(&mut state, &mut aligned)).unwrap(); - - let mut bootloader: BootLoader = BootLoader::new(ACTIVE, DFU, STATE); + let flash = flash.into_blocking(); + let mut bootloader = BootLoader::new(BootLoaderConfig { + active: flash.active(), + dfu: flash.dfu(), + state: flash.state(), + }); let mut page = [0; 4096]; - assert_eq!( - State::Swap, - bootloader - .prepare_boot( - &mut MultiFlashConfig::new(&mut active, &mut state, &mut dfu,), - &mut page - ) - .unwrap() - ); + assert_eq!(State::Swap, bootloader.prepare_boot(&mut page).unwrap()); - active.assert_eq(ACTIVE.from, &update); + let mut read_buf = [0; FIRMWARE_SIZE]; + flash.active().read(0, &mut read_buf).unwrap(); + assert_eq!(UPDATE, read_buf); // First DFU page is untouched - dfu.assert_eq(DFU.from + 4096, &original); + flash.dfu().read(4096, &mut read_buf).unwrap(); + assert_eq!(ORIGINAL, read_buf); } #[test] @@ -239,25 +273,25 @@ mod tests { let public_key: PublicKey = keypair.public; // Setup flash - - const STATE: Partition = Partition::new(0, 4096); - const DFU: Partition = Partition::new(4096, 8192); - let mut flash = MemFlash::<8192, 4096, 4>::default(); + let flash = BlockingTestFlash::new(BootLoaderConfig { + active: MemFlash::<0, 0, 0>::default(), + dfu: MemFlash::<4096, 4096, 4>::default(), + state: MemFlash::<4096, 4096, 4>::default(), + }); let firmware_len = firmware.len(); let mut write_buf = [0; 4096]; write_buf[0..firmware_len].copy_from_slice(firmware); - DFU.write_blocking(&mut flash, 0, &write_buf).unwrap(); + flash.dfu().write(0, &write_buf).unwrap(); // On with the test - - let mut updater = FirmwareUpdater::new(DFU, STATE); + let flash = flash.into_async(); + let mut updater = FirmwareUpdater::new(flash.dfu(), flash.state()); let mut aligned = [0; 4]; assert!(block_on(updater.verify_and_mark_updated( - &mut flash, &public_key.to_bytes(), &signature.to_bytes(), firmware_len as u32, diff --git a/embassy-boot/boot/src/test_flash/asynch.rs b/embassy-boot/boot/src/test_flash/asynch.rs index b5b3c253..3ac9e71a 100644 --- a/embassy-boot/boot/src/test_flash/asynch.rs +++ b/embassy-boot/boot/src/test_flash/asynch.rs @@ -3,6 +3,8 @@ use embassy_sync::blocking_mutex::raw::NoopRawMutex; use embassy_sync::mutex::Mutex; use embedded_storage_async::nor_flash::NorFlash; +use crate::BootLoaderConfig; + pub struct AsyncTestFlash where ACTIVE: NorFlash, @@ -20,11 +22,11 @@ where DFU: NorFlash, STATE: NorFlash, { - pub fn new(active: ACTIVE, dfu: DFU, state: STATE) -> Self { + pub fn new(config: BootLoaderConfig) -> Self { Self { - active: Mutex::new(active), - dfu: Mutex::new(dfu), - state: Mutex::new(state), + active: Mutex::new(config.active), + dfu: Mutex::new(config.dfu), + state: Mutex::new(config.state), } } @@ -52,6 +54,11 @@ where STATE: NorFlash + embedded_storage::nor_flash::NorFlash, { pub fn into_blocking(self) -> super::BlockingTestFlash { - super::BlockingTestFlash::new(self.active.into_inner(), self.dfu.into_inner(), self.state.into_inner()) + let config = BootLoaderConfig { + active: self.active.into_inner(), + dfu: self.dfu.into_inner(), + state: self.state.into_inner(), + }; + super::BlockingTestFlash::new(config) } } diff --git a/embassy-boot/boot/src/test_flash/blocking.rs b/embassy-boot/boot/src/test_flash/blocking.rs index 77876a21..ba33c920 100644 --- a/embassy-boot/boot/src/test_flash/blocking.rs +++ b/embassy-boot/boot/src/test_flash/blocking.rs @@ -5,6 +5,8 @@ use embassy_sync::blocking_mutex::raw::NoopRawMutex; use embassy_sync::blocking_mutex::Mutex; use embedded_storage::nor_flash::NorFlash; +use crate::BootLoaderConfig; + pub struct BlockingTestFlash where ACTIVE: NorFlash, @@ -22,11 +24,11 @@ where DFU: NorFlash, STATE: NorFlash, { - pub fn new(active: ACTIVE, dfu: DFU, state: STATE) -> Self { + pub fn new(config: BootLoaderConfig) -> Self { Self { - active: Mutex::new(RefCell::new(active)), - dfu: Mutex::new(RefCell::new(dfu)), - state: Mutex::new(RefCell::new(state)), + active: Mutex::new(RefCell::new(config.active)), + dfu: Mutex::new(RefCell::new(config.dfu)), + state: Mutex::new(RefCell::new(config.state)), } } @@ -49,6 +51,7 @@ where } } +#[cfg(feature = "nightly")] impl BlockingTestFlash where ACTIVE: NorFlash + embedded_storage_async::nor_flash::NorFlash, @@ -56,10 +59,11 @@ where STATE: NorFlash + embedded_storage_async::nor_flash::NorFlash, { pub fn into_async(self) -> super::AsyncTestFlash { - super::AsyncTestFlash::new( - self.active.into_inner().into_inner(), - self.dfu.into_inner().into_inner(), - self.state.into_inner().into_inner(), - ) + let config = BootLoaderConfig { + active: self.active.into_inner().into_inner(), + dfu: self.dfu.into_inner().into_inner(), + state: self.state.into_inner().into_inner(), + }; + super::AsyncTestFlash::new(config) } } From 54bbb4400d02e40d142d3f2cd9186c7892374d83 Mon Sep 17 00:00:00 2001 From: Rasmus Melchior Jacobsen Date: Tue, 30 May 2023 13:56:35 +0200 Subject: [PATCH 165/178] Align nrf --- embassy-boot/nrf/src/lib.rs | 71 +++++++++---------------------------- 1 file changed, 17 insertions(+), 54 deletions(-) diff --git a/embassy-boot/nrf/src/lib.rs b/embassy-boot/nrf/src/lib.rs index 710798bd..e26b07c9 100644 --- a/embassy-boot/nrf/src/lib.rs +++ b/embassy-boot/nrf/src/lib.rs @@ -3,74 +3,37 @@ #![doc = include_str!("../README.md")] mod fmt; -pub use embassy_boot::{AlignedBuffer, BootFlash, FirmwareUpdater, FlashConfig, Partition, SingleFlashConfig}; +#[cfg(feature = "nightly")] +pub use embassy_boot::FirmwareUpdater; +pub use embassy_boot::{AlignedBuffer, BlockingFirmwareUpdater, BootLoaderConfig, FirmwareUpdaterConfig}; use embassy_nrf::nvmc::{Nvmc, PAGE_SIZE}; use embassy_nrf::peripherals::WDT; use embassy_nrf::wdt; use embedded_storage::nor_flash::{ErrorType, NorFlash, ReadNorFlash}; /// A bootloader for nRF devices. -pub struct BootLoader { - boot: embassy_boot::BootLoader, +pub struct BootLoader { + boot: embassy_boot::BootLoader, aligned_buf: AlignedBuffer, } -#[cfg(target_os = "none")] -impl Default for BootLoader { - /// Create a new bootloader instance using parameters from linker script - fn default() -> Self { - extern "C" { - static __bootloader_state_start: u32; - static __bootloader_state_end: u32; - static __bootloader_active_start: u32; - static __bootloader_active_end: u32; - static __bootloader_dfu_start: u32; - static __bootloader_dfu_end: u32; - } - - let active = unsafe { - Partition::new( - &__bootloader_active_start as *const u32 as u32, - &__bootloader_active_end as *const u32 as u32, - ) - }; - let dfu = unsafe { - Partition::new( - &__bootloader_dfu_start as *const u32 as u32, - &__bootloader_dfu_end as *const u32 as u32, - ) - }; - let state = unsafe { - Partition::new( - &__bootloader_state_start as *const u32 as u32, - &__bootloader_state_end as *const u32 as u32, - ) - }; - - trace!("ACTIVE: 0x{:x} - 0x{:x}", active.from, active.to); - trace!("DFU: 0x{:x} - 0x{:x}", dfu.from, dfu.to); - trace!("STATE: 0x{:x} - 0x{:x}", state.from, state.to); - - Self::new(active, dfu, state) - } -} - -impl BootLoader { +impl + BootLoader +{ /// Create a new bootloader instance using the supplied partitions for active, dfu and state. - pub fn new(active: Partition, dfu: Partition, state: Partition) -> Self { + pub fn new(config: BootLoaderConfig) -> Self { Self { - boot: embassy_boot::BootLoader::new(active, dfu, state), + boot: embassy_boot::BootLoader::new(config), aligned_buf: AlignedBuffer([0; BUFFER_SIZE]), } } /// Inspect the bootloader state and perform actions required before booting, such as swapping /// firmware. - pub fn prepare(&mut self, flash: &mut F) -> usize { - match self.boot.prepare_boot(flash, &mut self.aligned_buf.0) { - Ok(_) => self.boot.boot_address(), - Err(_) => panic!("boot prepare error!"), - } + pub fn prepare(&mut self) { + self.boot + .prepare_boot(&mut self.aligned_buf.0) + .expect("Boot prepare error"); } /// Boots the application without softdevice mechanisms. @@ -79,10 +42,10 @@ impl BootLoader { /// /// This modifies the stack pointer and reset vector and will run code placed in the active partition. #[cfg(not(feature = "softdevice"))] - pub unsafe fn load(&mut self, start: usize) -> ! { + pub unsafe fn load(&mut self, start: u32) -> ! { let mut p = cortex_m::Peripherals::steal(); p.SCB.invalidate_icache(); - p.SCB.vtor.write(start as u32); + p.SCB.vtor.write(start); cortex_m::asm::bootload(start as *const u32) } @@ -92,7 +55,7 @@ impl BootLoader { /// /// This modifies the stack pointer and reset vector and will run code placed in the active partition. #[cfg(feature = "softdevice")] - pub unsafe fn load(&mut self, _app: usize) -> ! { + pub unsafe fn load(&mut self, _app: u32) -> ! { use nrf_softdevice_mbr as mbr; const NRF_SUCCESS: u32 = 0; From 24dee870a862d16fa2dc9be13d3f10dc3941b54b Mon Sep 17 00:00:00 2001 From: Rasmus Melchior Jacobsen Date: Tue, 30 May 2023 13:57:03 +0200 Subject: [PATCH 166/178] Align rp --- embassy-boot/rp/src/lib.rs | 69 +++++++++----------------------------- 1 file changed, 16 insertions(+), 53 deletions(-) diff --git a/embassy-boot/rp/src/lib.rs b/embassy-boot/rp/src/lib.rs index fb9bc324..e825a7d1 100644 --- a/embassy-boot/rp/src/lib.rs +++ b/embassy-boot/rp/src/lib.rs @@ -3,7 +3,9 @@ #![doc = include_str!("../README.md")] mod fmt; -pub use embassy_boot::{AlignedBuffer, BootFlash, FirmwareUpdater, FlashConfig, Partition, SingleFlashConfig, State}; +#[cfg(feature = "nightly")] +pub use embassy_boot::FirmwareUpdater; +pub use embassy_boot::{AlignedBuffer, BlockingFirmwareUpdater, BootLoaderConfig, FirmwareUpdaterConfig, State}; use embassy_rp::flash::{Flash, ERASE_SIZE}; use embassy_rp::peripherals::{FLASH, WATCHDOG}; use embassy_rp::watchdog::Watchdog; @@ -11,27 +13,28 @@ use embassy_time::Duration; use embedded_storage::nor_flash::{ErrorType, NorFlash, ReadNorFlash}; /// A bootloader for RP2040 devices. -pub struct BootLoader { - boot: embassy_boot::BootLoader, +pub struct BootLoader { + boot: embassy_boot::BootLoader, aligned_buf: AlignedBuffer, } -impl BootLoader { +impl + BootLoader +{ /// Create a new bootloader instance using the supplied partitions for active, dfu and state. - pub fn new(active: Partition, dfu: Partition, state: Partition) -> Self { + pub fn new(config: BootLoaderConfig) -> Self { Self { - boot: embassy_boot::BootLoader::new(active, dfu, state), + boot: embassy_boot::BootLoader::new(config), aligned_buf: AlignedBuffer([0; BUFFER_SIZE]), } } /// Inspect the bootloader state and perform actions required before booting, such as swapping /// firmware. - pub fn prepare(&mut self, flash: &mut F) -> usize { - match self.boot.prepare_boot(flash, self.aligned_buf.as_mut()) { - Ok(_) => embassy_rp::flash::FLASH_BASE + self.boot.boot_address(), - Err(_) => panic!("boot prepare error!"), - } + pub fn prepare(&mut self) { + self.boot + .prepare_boot(self.aligned_buf.as_mut()) + .expect("Boot prepare error"); } /// Boots the application. @@ -39,58 +42,18 @@ impl BootLoader { /// # Safety /// /// This modifies the stack pointer and reset vector and will run code placed in the active partition. - pub unsafe fn load(&mut self, start: usize) -> ! { + pub unsafe fn load(&mut self, start: u32) -> ! { trace!("Loading app at 0x{:x}", start); #[allow(unused_mut)] let mut p = cortex_m::Peripherals::steal(); #[cfg(not(armv6m))] p.SCB.invalidate_icache(); - p.SCB.vtor.write(start as u32); + p.SCB.vtor.write(start); cortex_m::asm::bootload(start as *const u32) } } -#[cfg(target_os = "none")] -impl Default for BootLoader { - /// Create a new bootloader instance using parameters from linker script - fn default() -> Self { - extern "C" { - static __bootloader_state_start: u32; - static __bootloader_state_end: u32; - static __bootloader_active_start: u32; - static __bootloader_active_end: u32; - static __bootloader_dfu_start: u32; - static __bootloader_dfu_end: u32; - } - - let active = unsafe { - Partition::new( - &__bootloader_active_start as *const u32 as u32, - &__bootloader_active_end as *const u32 as u32, - ) - }; - let dfu = unsafe { - Partition::new( - &__bootloader_dfu_start as *const u32 as u32, - &__bootloader_dfu_end as *const u32 as u32, - ) - }; - let state = unsafe { - Partition::new( - &__bootloader_state_start as *const u32 as u32, - &__bootloader_state_end as *const u32 as u32, - ) - }; - - trace!("ACTIVE: 0x{:x} - 0x{:x}", active.from, active.to); - trace!("DFU: 0x{:x} - 0x{:x}", dfu.from, dfu.to); - trace!("STATE: 0x{:x} - 0x{:x}", state.from, state.to); - - Self::new(active, dfu, state) - } -} - /// A flash implementation that will feed a watchdog when touching flash. pub struct WatchdogFlash<'d, const SIZE: usize> { flash: Flash<'d, FLASH, SIZE>, From 887ecef3690b55234e640ae16d33a2d79a822bf0 Mon Sep 17 00:00:00 2001 From: Rasmus Melchior Jacobsen Date: Tue, 30 May 2023 13:57:19 +0200 Subject: [PATCH 167/178] Align stm32 --- embassy-boot/stm32/src/lib.rs | 70 +++++++++-------------------------- 1 file changed, 17 insertions(+), 53 deletions(-) diff --git a/embassy-boot/stm32/src/lib.rs b/embassy-boot/stm32/src/lib.rs index ccf136c7..b47b2db4 100644 --- a/embassy-boot/stm32/src/lib.rs +++ b/embassy-boot/stm32/src/lib.rs @@ -3,30 +3,34 @@ #![doc = include_str!("../README.md")] mod fmt; -pub use embassy_boot::{AlignedBuffer, BootFlash, FirmwareUpdater, FlashConfig, Partition, SingleFlashConfig, State}; +#[cfg(feature = "nightly")] +pub use embassy_boot::FirmwareUpdater; +pub use embassy_boot::{AlignedBuffer, BlockingFirmwareUpdater, BootLoaderConfig, FirmwareUpdaterConfig, State}; +use embedded_storage::nor_flash::NorFlash; /// A bootloader for STM32 devices. -pub struct BootLoader { - boot: embassy_boot::BootLoader, +pub struct BootLoader { + boot: embassy_boot::BootLoader, aligned_buf: AlignedBuffer, } -impl BootLoader { +impl + BootLoader +{ /// Create a new bootloader instance using the supplied partitions for active, dfu and state. - pub fn new(active: Partition, dfu: Partition, state: Partition) -> Self { + pub fn new(config: BootLoaderConfig) -> Self { Self { - boot: embassy_boot::BootLoader::new(active, dfu, state), + boot: embassy_boot::BootLoader::new(config), aligned_buf: AlignedBuffer([0; BUFFER_SIZE]), } } /// Inspect the bootloader state and perform actions required before booting, such as swapping /// firmware. - pub fn prepare(&mut self, flash: &mut F) -> usize { - match self.boot.prepare_boot(flash, self.aligned_buf.as_mut()) { - Ok(_) => embassy_stm32::flash::FLASH_BASE + self.boot.boot_address(), - Err(_) => panic!("boot prepare error!"), - } + pub fn prepare(&mut self) { + self.boot + .prepare_boot(self.aligned_buf.as_mut()) + .expect("Boot prepare error"); } /// Boots the application. @@ -34,54 +38,14 @@ impl BootLoader { /// # Safety /// /// This modifies the stack pointer and reset vector and will run code placed in the active partition. - pub unsafe fn load(&mut self, start: usize) -> ! { + pub unsafe fn load(&mut self, start: u32) -> ! { trace!("Loading app at 0x{:x}", start); #[allow(unused_mut)] let mut p = cortex_m::Peripherals::steal(); #[cfg(not(armv6m))] p.SCB.invalidate_icache(); - p.SCB.vtor.write(start as u32); + p.SCB.vtor.write(start); cortex_m::asm::bootload(start as *const u32) } } - -#[cfg(target_os = "none")] -impl Default for BootLoader { - /// Create a new bootloader instance using parameters from linker script - fn default() -> Self { - extern "C" { - static __bootloader_state_start: u32; - static __bootloader_state_end: u32; - static __bootloader_active_start: u32; - static __bootloader_active_end: u32; - static __bootloader_dfu_start: u32; - static __bootloader_dfu_end: u32; - } - - let active = unsafe { - Partition::new( - &__bootloader_active_start as *const u32 as u32, - &__bootloader_active_end as *const u32 as u32, - ) - }; - let dfu = unsafe { - Partition::new( - &__bootloader_dfu_start as *const u32 as u32, - &__bootloader_dfu_end as *const u32 as u32, - ) - }; - let state = unsafe { - Partition::new( - &__bootloader_state_start as *const u32 as u32, - &__bootloader_state_end as *const u32 as u32, - ) - }; - - trace!("ACTIVE: 0x{:x} - 0x{:x}", active.from, active.to); - trace!("DFU: 0x{:x} - 0x{:x}", dfu.from, dfu.to); - trace!("STATE: 0x{:x} - 0x{:x}", state.from, state.to); - - Self::new(active, dfu, state) - } -} From c2aca45b8d3785007da20ce007d6a6e352fac1a0 Mon Sep 17 00:00:00 2001 From: Rasmus Melchior Jacobsen Date: Tue, 30 May 2023 13:57:40 +0200 Subject: [PATCH 168/178] Add offset and size accessors to Partition --- embassy-embedded-hal/src/flash/partition/asynch.rs | 10 ++++++++++ embassy-embedded-hal/src/flash/partition/blocking.rs | 10 ++++++++++ 2 files changed, 20 insertions(+) diff --git a/embassy-embedded-hal/src/flash/partition/asynch.rs b/embassy-embedded-hal/src/flash/partition/asynch.rs index 141e0d9f..5920436d 100644 --- a/embassy-embedded-hal/src/flash/partition/asynch.rs +++ b/embassy-embedded-hal/src/flash/partition/asynch.rs @@ -30,6 +30,16 @@ impl<'a, M: RawMutex, T: NorFlash> Partition<'a, M, T> { } Self { flash, offset, size } } + + /// Get the partition offset within the flash + pub const fn offset(&self) -> u32 { + self.offset + } + + /// Get the partition size + pub const fn size(&self) -> u32 { + self.size + } } impl ErrorType for Partition<'_, M, T> { diff --git a/embassy-embedded-hal/src/flash/partition/blocking.rs b/embassy-embedded-hal/src/flash/partition/blocking.rs index dc52e292..2ddbe3de 100644 --- a/embassy-embedded-hal/src/flash/partition/blocking.rs +++ b/embassy-embedded-hal/src/flash/partition/blocking.rs @@ -31,6 +31,16 @@ impl<'a, M: RawMutex, T: NorFlash> BlockingPartition<'a, M, T> { } Self { flash, offset, size } } + + /// Get the partition offset within the flash + pub const fn offset(&self) -> u32 { + self.offset + } + + /// Get the partition size + pub const fn size(&self) -> u32 { + self.size + } } impl ErrorType for BlockingPartition<'_, M, T> { From 36e00caf4dc70905b735531c0d5634addd026954 Mon Sep 17 00:00:00 2001 From: Rasmus Melchior Jacobsen Date: Tue, 30 May 2023 14:03:31 +0200 Subject: [PATCH 169/178] Align examples --- embassy-boot/nrf/src/lib.rs | 4 +++- embassy-boot/rp/src/lib.rs | 4 +++- embassy-boot/stm32/src/lib.rs | 4 +++- examples/boot/application/nrf/src/bin/a.rs | 12 ++++++----- examples/boot/application/rp/Cargo.toml | 1 + examples/boot/application/rp/src/bin/a.rs | 19 ++++++++++------- .../boot/application/stm32f3/src/bin/a.rs | 12 ++++++----- .../boot/application/stm32f7/src/bin/a.rs | 14 +++++++------ .../boot/application/stm32h7/src/bin/a.rs | 15 ++++++------- .../boot/application/stm32l1/src/bin/a.rs | 11 +++++----- .../boot/application/stm32l4/src/bin/a.rs | 9 ++++---- .../boot/application/stm32wl/src/bin/a.rs | 9 ++++---- examples/boot/bootloader/nrf/Cargo.toml | 1 + examples/boot/bootloader/nrf/src/main.rs | 21 ++++++++++++------- examples/boot/bootloader/rp/Cargo.toml | 1 + examples/boot/bootloader/rp/src/main.rs | 16 +++++++++----- examples/boot/bootloader/stm32/Cargo.toml | 1 + examples/boot/bootloader/stm32/src/main.rs | 19 +++++++++++------ 18 files changed, 108 insertions(+), 65 deletions(-) diff --git a/embassy-boot/nrf/src/lib.rs b/embassy-boot/nrf/src/lib.rs index e26b07c9..bb702073 100644 --- a/embassy-boot/nrf/src/lib.rs +++ b/embassy-boot/nrf/src/lib.rs @@ -42,7 +42,9 @@ impl /// /// This modifies the stack pointer and reset vector and will run code placed in the active partition. #[cfg(not(feature = "softdevice"))] - pub unsafe fn load(&mut self, start: u32) -> ! { + pub unsafe fn load(self, start: u32) -> ! { + core::mem::drop(self.boot); + let mut p = cortex_m::Peripherals::steal(); p.SCB.invalidate_icache(); p.SCB.vtor.write(start); diff --git a/embassy-boot/rp/src/lib.rs b/embassy-boot/rp/src/lib.rs index e825a7d1..25329f9e 100644 --- a/embassy-boot/rp/src/lib.rs +++ b/embassy-boot/rp/src/lib.rs @@ -42,7 +42,9 @@ impl /// # Safety /// /// This modifies the stack pointer and reset vector and will run code placed in the active partition. - pub unsafe fn load(&mut self, start: u32) -> ! { + pub unsafe fn load(self, start: u32) -> ! { + core::mem::drop(self.boot); + trace!("Loading app at 0x{:x}", start); #[allow(unused_mut)] let mut p = cortex_m::Peripherals::steal(); diff --git a/embassy-boot/stm32/src/lib.rs b/embassy-boot/stm32/src/lib.rs index b47b2db4..069de0d1 100644 --- a/embassy-boot/stm32/src/lib.rs +++ b/embassy-boot/stm32/src/lib.rs @@ -38,7 +38,9 @@ impl /// # Safety /// /// This modifies the stack pointer and reset vector and will run code placed in the active partition. - pub unsafe fn load(&mut self, start: u32) -> ! { + pub unsafe fn load(self, start: u32) -> ! { + core::mem::drop(self.boot); + trace!("Loading app at 0x{:x}", start); #[allow(unused_mut)] let mut p = cortex_m::Peripherals::steal(); diff --git a/examples/boot/application/nrf/src/bin/a.rs b/examples/boot/application/nrf/src/bin/a.rs index 090a05b2..06c23778 100644 --- a/examples/boot/application/nrf/src/bin/a.rs +++ b/examples/boot/application/nrf/src/bin/a.rs @@ -3,12 +3,13 @@ #![macro_use] #![feature(type_alias_impl_trait)] -use embassy_boot_nrf::FirmwareUpdater; +use embassy_boot_nrf::{FirmwareUpdater, FirmwareUpdaterConfig}; use embassy_embedded_hal::adapter::BlockingAsync; use embassy_executor::Spawner; use embassy_nrf::gpio::{Input, Level, Output, OutputDrive, Pull}; use embassy_nrf::nvmc::Nvmc; use embassy_nrf::wdt::{self, Watchdog}; +use embassy_sync::mutex::Mutex; use panic_reset as _; static APP_B: &[u8] = include_bytes!("../../b.bin"); @@ -45,9 +46,10 @@ async fn main(_spawner: Spawner) { }; let nvmc = Nvmc::new(p.NVMC); - let mut nvmc = BlockingAsync::new(nvmc); + let nvmc = Mutex::new(BlockingAsync::new(nvmc)); - let mut updater = FirmwareUpdater::default(); + let config = FirmwareUpdaterConfig::from_linkerfile(&nvmc); + let mut updater = FirmwareUpdater::new(config); loop { led.set_low(); button.wait_for_any_edge().await; @@ -56,11 +58,11 @@ async fn main(_spawner: Spawner) { for chunk in APP_B.chunks(4096) { let mut buf: [u8; 4096] = [0; 4096]; buf[..chunk.len()].copy_from_slice(chunk); - updater.write_firmware(offset, &buf, &mut nvmc, 4096).await.unwrap(); + updater.write_firmware(offset, &buf).await.unwrap(); offset += chunk.len(); } let mut magic = [0; 4]; - updater.mark_updated(&mut nvmc, &mut magic).await.unwrap(); + updater.mark_updated(&mut magic).await.unwrap(); led.set_high(); cortex_m::peripheral::SCB::sys_reset(); } diff --git a/examples/boot/application/rp/Cargo.toml b/examples/boot/application/rp/Cargo.toml index 64c2b892..4a2c5dd8 100644 --- a/examples/boot/application/rp/Cargo.toml +++ b/examples/boot/application/rp/Cargo.toml @@ -20,6 +20,7 @@ embedded-hal = { version = "0.2.6" } cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } cortex-m-rt = "0.7.0" +embedded-storage = "0.3.0" [features] default = ["panic-reset"] diff --git a/examples/boot/application/rp/src/bin/a.rs b/examples/boot/application/rp/src/bin/a.rs index 47f1d16d..3fa908b6 100644 --- a/examples/boot/application/rp/src/bin/a.rs +++ b/examples/boot/application/rp/src/bin/a.rs @@ -9,6 +9,9 @@ use embassy_rp::flash::Flash; use embassy_rp::gpio::{Level, Output}; use embassy_rp::watchdog::Watchdog; use embassy_time::{Duration, Timer}; +use embassy_sync::blocking_mutex::Mutex; +use core::cell::RefCell; +use embedded_storage::nor_flash::NorFlash; #[cfg(feature = "panic-probe")] use panic_probe as _; #[cfg(feature = "panic-reset")] @@ -26,9 +29,11 @@ async fn main(_s: Spawner) { let mut watchdog = Watchdog::new(p.WATCHDOG); watchdog.start(Duration::from_secs(8)); - let mut flash: Flash<_, FLASH_SIZE> = Flash::new_blocking(p.FLASH); + let flash: Flash<_, FLASH_SIZE> = Flash::new(p.FLASH); + let flash = Mutex::new(RefCell::new(flash)); - let mut updater = FirmwareUpdater::default(); + let config = FirmwareUpdaterConfig::from_linkerfile_blocking(&flash); + let mut updater = BlockingFirmwareUpdater::new(config); Timer::after(Duration::from_secs(5)).await; watchdog.feed(); @@ -36,8 +41,8 @@ async fn main(_s: Spawner) { let mut offset = 0; let mut buf: AlignedBuffer<4096> = AlignedBuffer([0; 4096]); defmt::info!("preparing update"); - let mut writer = updater - .prepare_update_blocking(&mut flash) + let writer = updater + .prepare_update() .map_err(|e| defmt::warn!("E: {:?}", defmt::Debug2Format(&e))) .unwrap(); defmt::info!("writer created, starting write"); @@ -45,13 +50,13 @@ async fn main(_s: Spawner) { buf.0[..chunk.len()].copy_from_slice(chunk); defmt::info!("writing block at offset {}", offset); writer - .write_block_blocking(offset, &buf.0[..], &mut flash, 256) + .write(offset, &buf.0[..]) .unwrap(); - offset += chunk.len(); + offset += chunk.len() as u32; } watchdog.feed(); defmt::info!("firmware written, marking update"); - updater.mark_updated_blocking(&mut flash, &mut buf.0[..1]).unwrap(); + updater.mark_updated(&mut buf.0[..1]).unwrap(); Timer::after(Duration::from_secs(2)).await; led.set_low(); defmt::info!("update marked, resetting"); diff --git a/examples/boot/application/stm32f3/src/bin/a.rs b/examples/boot/application/stm32f3/src/bin/a.rs index 5db1dbb5..6a5c276f 100644 --- a/examples/boot/application/stm32f3/src/bin/a.rs +++ b/examples/boot/application/stm32f3/src/bin/a.rs @@ -4,7 +4,8 @@ #[cfg(feature = "defmt-rtt")] use defmt_rtt::*; -use embassy_boot_stm32::{AlignedBuffer, FirmwareUpdater}; +use embassy_boot_stm32::{AlignedBuffer, FirmwareUpdater, FirmwareUpdaterConfig}; +use embassy_sync::mutex::Mutex; use embassy_embedded_hal::adapter::BlockingAsync; use embassy_executor::Spawner; use embassy_stm32::exti::ExtiInput; @@ -18,7 +19,7 @@ static APP_B: &[u8] = include_bytes!("../../b.bin"); async fn main(_spawner: Spawner) { let p = embassy_stm32::init(Default::default()); let flash = Flash::new_blocking(p.FLASH); - let mut flash = BlockingAsync::new(flash); + let flash = Mutex::new(BlockingAsync::new(flash)); let button = Input::new(p.PC13, Pull::Up); let mut button = ExtiInput::new(button, p.EXTI13); @@ -26,17 +27,18 @@ async fn main(_spawner: Spawner) { let mut led = Output::new(p.PA5, Level::Low, Speed::Low); led.set_high(); - let mut updater = FirmwareUpdater::default(); + let config = FirmwareUpdaterConfig::from_linkerfile(&flash); + let mut updater = FirmwareUpdater::new(config); button.wait_for_falling_edge().await; let mut offset = 0; for chunk in APP_B.chunks(2048) { let mut buf: [u8; 2048] = [0; 2048]; buf[..chunk.len()].copy_from_slice(chunk); - updater.write_firmware(offset, &buf, &mut flash, 2048).await.unwrap(); + updater.write_firmware(offset, &buf).await.unwrap(); offset += chunk.len(); } let mut magic = AlignedBuffer([0; WRITE_SIZE]); - updater.mark_updated(&mut flash, magic.as_mut()).await.unwrap(); + updater.mark_updated(magic.as_mut()).await.unwrap(); led.set_low(); cortex_m::peripheral::SCB::sys_reset(); } diff --git a/examples/boot/application/stm32f7/src/bin/a.rs b/examples/boot/application/stm32f7/src/bin/a.rs index 5d586445..530683ca 100644 --- a/examples/boot/application/stm32f7/src/bin/a.rs +++ b/examples/boot/application/stm32f7/src/bin/a.rs @@ -4,7 +4,7 @@ #[cfg(feature = "defmt-rtt")] use defmt_rtt::*; -use embassy_boot_stm32::{AlignedBuffer, FirmwareUpdater}; +use embassy_boot_stm32::{AlignedBuffer, BlockingFirmwareUpdater, FirmwareUpdaterConfig}; use embassy_executor::Spawner; use embassy_stm32::exti::ExtiInput; use embassy_stm32::flash::{Flash, WRITE_SIZE}; @@ -16,7 +16,8 @@ static APP_B: &[u8] = include_bytes!("../../b.bin"); #[embassy_executor::main] async fn main(_spawner: Spawner) { let p = embassy_stm32::init(Default::default()); - let mut flash = Flash::new_blocking(p.FLASH); + let flash = Flash::new_blocking(p.FLASH); + let flash = Mutex::new(RefCell::new(flash)); let button = Input::new(p.PC13, Pull::Down); let mut button = ExtiInput::new(button, p.EXTI13); @@ -24,20 +25,21 @@ async fn main(_spawner: Spawner) { let mut led = Output::new(p.PB7, Level::Low, Speed::Low); led.set_high(); - let mut updater = FirmwareUpdater::default(); - let mut writer = updater.prepare_update_blocking(&mut flash).unwrap(); + let config = FirmwareUpdaterConfig::from_linkerfile_blocking(&flash); + let mut updater = BlockingFirmwareUpdater::new(config); + let mut writer = updater.prepare_update().unwrap(); button.wait_for_rising_edge().await; let mut offset = 0; let mut buf = AlignedBuffer([0; 4096]); for chunk in APP_B.chunks(4096) { buf.as_mut()[..chunk.len()].copy_from_slice(chunk); writer - .write_block_blocking(offset, buf.as_ref(), &mut flash, chunk.len()) + .write(offset, buf.as_ref()) .unwrap(); offset += chunk.len(); } let mut magic = AlignedBuffer([0; WRITE_SIZE]); - updater.mark_updated_blocking(&mut flash, magic.as_mut()).unwrap(); + updater.mark_updated(magic.as_mut()).unwrap(); led.set_low(); cortex_m::peripheral::SCB::sys_reset(); } diff --git a/examples/boot/application/stm32h7/src/bin/a.rs b/examples/boot/application/stm32h7/src/bin/a.rs index 20222022..e5f94310 100644 --- a/examples/boot/application/stm32h7/src/bin/a.rs +++ b/examples/boot/application/stm32h7/src/bin/a.rs @@ -4,7 +4,7 @@ #[cfg(feature = "defmt-rtt")] use defmt_rtt::*; -use embassy_boot_stm32::{AlignedBuffer, FirmwareUpdater}; +use embassy_boot_stm32::{AlignedBuffer, BlockingFirmwareUpdater, FirmwareUpdaterConfig}; use embassy_executor::Spawner; use embassy_stm32::exti::ExtiInput; use embassy_stm32::flash::{Flash, WRITE_SIZE}; @@ -16,7 +16,8 @@ static APP_B: &[u8] = include_bytes!("../../b.bin"); #[embassy_executor::main] async fn main(_spawner: Spawner) { let p = embassy_stm32::init(Default::default()); - let mut flash = Flash::new_blocking(p.FLASH); + let flash = Flash::new_blocking(p.FLASH); + let flash = Mutex::new(RefCell::new(flash)); let button = Input::new(p.PC13, Pull::Down); let mut button = ExtiInput::new(button, p.EXTI13); @@ -24,21 +25,21 @@ async fn main(_spawner: Spawner) { let mut led = Output::new(p.PB14, Level::Low, Speed::Low); led.set_high(); - let mut updater = FirmwareUpdater::default(); - - let mut writer = updater.prepare_update_blocking(&mut flash).unwrap(); + let config = FirmwareUpdaterConfig::from_linkerfile_blocking(&flash); + let mut updater = BlockingFirmwareUpdater::new(config); + let mut writer = updater.prepare_update().unwrap(); button.wait_for_rising_edge().await; let mut offset = 0; let mut buf = AlignedBuffer([0; 4096]); for chunk in APP_B.chunks(4096) { buf.as_mut()[..chunk.len()].copy_from_slice(chunk); writer - .write_block_blocking(offset, buf.as_ref(), &mut flash, 4096) + .write(offset, buf.as_ref()) .unwrap(); offset += chunk.len(); } let mut magic = AlignedBuffer([0; WRITE_SIZE]); - updater.mark_updated_blocking(&mut flash, magic.as_mut()).unwrap(); + updater.mark_updated(magic.as_mut()).unwrap(); led.set_low(); cortex_m::peripheral::SCB::sys_reset(); } diff --git a/examples/boot/application/stm32l1/src/bin/a.rs b/examples/boot/application/stm32l1/src/bin/a.rs index 4033ac59..00ddda63 100644 --- a/examples/boot/application/stm32l1/src/bin/a.rs +++ b/examples/boot/application/stm32l1/src/bin/a.rs @@ -4,7 +4,7 @@ #[cfg(feature = "defmt-rtt")] use defmt_rtt::*; -use embassy_boot_stm32::{AlignedBuffer, FirmwareUpdater}; +use embassy_boot_stm32::{AlignedBuffer, FirmwareUpdater, FirmwareUpdaterConfig}; use embassy_embedded_hal::adapter::BlockingAsync; use embassy_executor::Spawner; use embassy_stm32::exti::ExtiInput; @@ -19,7 +19,7 @@ static APP_B: &[u8] = include_bytes!("../../b.bin"); async fn main(_spawner: Spawner) { let p = embassy_stm32::init(Default::default()); let flash = Flash::new_blocking(p.FLASH); - let mut flash = BlockingAsync::new(flash); + let flash = Mutex::new(BlockingAsync::new(flash)); let button = Input::new(p.PB2, Pull::Up); let mut button = ExtiInput::new(button, p.EXTI2); @@ -28,18 +28,19 @@ async fn main(_spawner: Spawner) { led.set_high(); - let mut updater = FirmwareUpdater::default(); + let config = FirmwareUpdaterConfig::from_linkerfile(&flash); + let mut updater = FirmwareUpdater::new(config); button.wait_for_falling_edge().await; let mut offset = 0; for chunk in APP_B.chunks(128) { let mut buf: [u8; 128] = [0; 128]; buf[..chunk.len()].copy_from_slice(chunk); - updater.write_firmware(offset, &buf, &mut flash, 128).await.unwrap(); + updater.write_firmware(offset, &buf).await.unwrap(); offset += chunk.len(); } let mut magic = AlignedBuffer([0; WRITE_SIZE]); - updater.mark_updated(&mut flash, magic.as_mut()).await.unwrap(); + updater.mark_updated(magic.as_mut()).await.unwrap(); led.set_low(); Timer::after(Duration::from_secs(1)).await; cortex_m::peripheral::SCB::sys_reset(); diff --git a/examples/boot/application/stm32l4/src/bin/a.rs b/examples/boot/application/stm32l4/src/bin/a.rs index 141d82af..54579e4a 100644 --- a/examples/boot/application/stm32l4/src/bin/a.rs +++ b/examples/boot/application/stm32l4/src/bin/a.rs @@ -4,7 +4,7 @@ #[cfg(feature = "defmt-rtt")] use defmt_rtt::*; -use embassy_boot_stm32::{AlignedBuffer, FirmwareUpdater}; +use embassy_boot_stm32::{AlignedBuffer, FirmwareUpdater, FirmwareUpdaterConfig}; use embassy_embedded_hal::adapter::BlockingAsync; use embassy_executor::Spawner; use embassy_stm32::exti::ExtiInput; @@ -18,7 +18,7 @@ static APP_B: &[u8] = include_bytes!("../../b.bin"); async fn main(_spawner: Spawner) { let p = embassy_stm32::init(Default::default()); let flash = Flash::new_blocking(p.FLASH); - let mut flash = BlockingAsync::new(flash); + let flash = Mutex::new(BlockingAsync::new(flash)); let button = Input::new(p.PC13, Pull::Up); let mut button = ExtiInput::new(button, p.EXTI13); @@ -26,13 +26,14 @@ async fn main(_spawner: Spawner) { let mut led = Output::new(p.PB14, Level::Low, Speed::Low); led.set_high(); - let mut updater = FirmwareUpdater::default(); + let config = FirmwareUpdaterConfig::from_linkerfile(&flash); + let mut updater = FirmwareUpdater::new(config); button.wait_for_falling_edge().await; let mut offset = 0; for chunk in APP_B.chunks(2048) { let mut buf: [u8; 2048] = [0; 2048]; buf[..chunk.len()].copy_from_slice(chunk); - updater.write_firmware(offset, &buf, &mut flash, 2048).await.unwrap(); + updater.write_firmware(offset, &buf).await.unwrap(); offset += chunk.len(); } let mut magic = AlignedBuffer([0; WRITE_SIZE]); diff --git a/examples/boot/application/stm32wl/src/bin/a.rs b/examples/boot/application/stm32wl/src/bin/a.rs index 5f48dbe5..0c6fa05f 100644 --- a/examples/boot/application/stm32wl/src/bin/a.rs +++ b/examples/boot/application/stm32wl/src/bin/a.rs @@ -18,7 +18,7 @@ static APP_B: &[u8] = include_bytes!("../../b.bin"); async fn main(_spawner: Spawner) { let p = embassy_stm32::init(Default::default()); let flash = Flash::new_blocking(p.FLASH); - let mut flash = BlockingAsync::new(flash); + let mut flash = Mutex::new(BlockingAsync::new(flash)); let button = Input::new(p.PA0, Pull::Up); let mut button = ExtiInput::new(button, p.EXTI0); @@ -26,7 +26,8 @@ async fn main(_spawner: Spawner) { let mut led = Output::new(p.PB9, Level::Low, Speed::Low); led.set_high(); - let mut updater = FirmwareUpdater::default(); + let config = FirmwareUpdaterConfig::from_linkerfile(&flash); + let mut updater = FirmwareUpdater::new(config); button.wait_for_falling_edge().await; //defmt::info!("Starting update"); let mut offset = 0; @@ -34,11 +35,11 @@ async fn main(_spawner: Spawner) { let mut buf: [u8; 2048] = [0; 2048]; buf[..chunk.len()].copy_from_slice(chunk); // defmt::info!("Writing chunk at 0x{:x}", offset); - updater.write_firmware(offset, &buf, &mut flash, 2048).await.unwrap(); + updater.write_firmware(offset, &buf).await.unwrap(); offset += chunk.len(); } let mut magic = AlignedBuffer([0; WRITE_SIZE]); - updater.mark_updated(&mut flash, magic.as_mut()).await.unwrap(); + updater.mark_updated(magic.as_mut()).await.unwrap(); //defmt::info!("Marked as updated"); led.set_low(); cortex_m::peripheral::SCB::sys_reset(); diff --git a/examples/boot/bootloader/nrf/Cargo.toml b/examples/boot/bootloader/nrf/Cargo.toml index 8c2fb4c5..40656f35 100644 --- a/examples/boot/bootloader/nrf/Cargo.toml +++ b/examples/boot/bootloader/nrf/Cargo.toml @@ -12,6 +12,7 @@ defmt-rtt = { version = "0.4", optional = true } embassy-nrf = { path = "../../../../embassy-nrf", features = ["nightly"] } embassy-boot-nrf = { path = "../../../../embassy-boot/nrf" } cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } +embassy-sync = { path = "../../../../embassy-sync" } cortex-m-rt = { version = "0.7" } cfg-if = "1.0.0" diff --git a/examples/boot/bootloader/nrf/src/main.rs b/examples/boot/bootloader/nrf/src/main.rs index 8818a23b..72c95c02 100644 --- a/examples/boot/bootloader/nrf/src/main.rs +++ b/examples/boot/bootloader/nrf/src/main.rs @@ -1,12 +1,15 @@ #![no_std] #![no_main] +use core::cell::RefCell; + use cortex_m_rt::{entry, exception}; #[cfg(feature = "defmt")] use defmt_rtt as _; use embassy_boot_nrf::*; use embassy_nrf::nvmc::Nvmc; use embassy_nrf::wdt; +use embassy_sync::blocking_mutex::Mutex; #[entry] fn main() -> ! { @@ -20,19 +23,21 @@ fn main() -> ! { } */ - let mut bl = BootLoader::default(); - let mut wdt_config = wdt::Config::default(); wdt_config.timeout_ticks = 32768 * 5; // timeout seconds wdt_config.run_during_sleep = true; wdt_config.run_during_debug_halt = false; - let start = bl.prepare(&mut SingleFlashConfig::new(&mut BootFlash::new(WatchdogFlash::start( - Nvmc::new(p.NVMC), - p.WDT, - wdt_config, - )))); - unsafe { bl.load(start) } + let flash = WatchdogFlash::start(Nvmc::new(p.NVMC), p.WDT, wdt_config); + let flash = Mutex::new(RefCell::new(flash)); + + let config = BootLoaderConfig::from_linkerfile_blocking(&flash); + let active_offset = config.active.offset(); + let mut bl: BootLoader<_, _, _> = BootLoader::new(config); + + bl.prepare(); + + unsafe { bl.load(active_offset) } } #[no_mangle] diff --git a/examples/boot/bootloader/rp/Cargo.toml b/examples/boot/bootloader/rp/Cargo.toml index bf922699..8d60f18b 100644 --- a/examples/boot/bootloader/rp/Cargo.toml +++ b/examples/boot/bootloader/rp/Cargo.toml @@ -11,6 +11,7 @@ defmt-rtt = { version = "0.4", optional = true } embassy-rp = { path = "../../../../embassy-rp", features = ["nightly"] } embassy-boot-rp = { path = "../../../../embassy-boot/rp" } +embassy-sync = { path = "../../../../embassy-sync" } embassy-time = { path = "../../../../embassy-time", features = ["nightly"] } cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } diff --git a/examples/boot/bootloader/rp/src/main.rs b/examples/boot/bootloader/rp/src/main.rs index 8129591f..6a81db80 100644 --- a/examples/boot/bootloader/rp/src/main.rs +++ b/examples/boot/bootloader/rp/src/main.rs @@ -1,10 +1,13 @@ #![no_std] #![no_main] +use core::cell::RefCell; + use cortex_m_rt::{entry, exception}; #[cfg(feature = "defmt")] use defmt_rtt as _; use embassy_boot_rp::*; +use embassy_sync::blocking_mutex::Mutex; use embassy_time::Duration; const FLASH_SIZE: usize = 2 * 1024 * 1024; @@ -21,13 +24,16 @@ fn main() -> ! { } */ - let mut bl: BootLoader = BootLoader::default(); let flash = WatchdogFlash::::start(p.FLASH, p.WATCHDOG, Duration::from_secs(8)); - let mut flash = BootFlash::new(flash); - let start = bl.prepare(&mut SingleFlashConfig::new(&mut flash)); - core::mem::drop(flash); + let flash = Mutex::new(RefCell::new(flash)); - unsafe { bl.load(start) } + let config = BootLoaderConfig::from_linkerfile_blocking(&flash); + let active_offset = config.active.offset(); + let mut bl: BootLoader<_, _, _> = BootLoader::new(config); + + bl.prepare(); + + unsafe { bl.load(embassy_rp::flash::FLASH_BASE as u32 + active_offset) } } #[no_mangle] diff --git a/examples/boot/bootloader/stm32/Cargo.toml b/examples/boot/bootloader/stm32/Cargo.toml index fbc80b34..6436f2fe 100644 --- a/examples/boot/bootloader/stm32/Cargo.toml +++ b/examples/boot/bootloader/stm32/Cargo.toml @@ -12,6 +12,7 @@ defmt-rtt = { version = "0.4", optional = true } embassy-stm32 = { path = "../../../../embassy-stm32", features = ["nightly"] } embassy-boot-stm32 = { path = "../../../../embassy-boot/stm32" } cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } +embassy-sync = { path = "../../../../embassy-sync" } cortex-m-rt = { version = "0.7" } embedded-storage = "0.3.0" embedded-storage-async = "0.4.0" diff --git a/examples/boot/bootloader/stm32/src/main.rs b/examples/boot/bootloader/stm32/src/main.rs index f81fdbc5..262eed20 100644 --- a/examples/boot/bootloader/stm32/src/main.rs +++ b/examples/boot/bootloader/stm32/src/main.rs @@ -1,11 +1,14 @@ #![no_std] #![no_main] +use core::cell::RefCell; + use cortex_m_rt::{entry, exception}; #[cfg(feature = "defmt")] use defmt_rtt as _; use embassy_boot_stm32::*; -use embassy_stm32::flash::Flash; +use embassy_stm32::flash::{Flash, BANK1_REGION}; +use embassy_sync::blocking_mutex::Mutex; #[entry] fn main() -> ! { @@ -19,12 +22,16 @@ fn main() -> ! { } */ - let mut bl: BootLoader<2048> = BootLoader::default(); let layout = Flash::new_blocking(p.FLASH).into_blocking_regions(); - let mut flash = BootFlash::new(layout.bank1_region); - let start = bl.prepare(&mut SingleFlashConfig::new(&mut flash)); - core::mem::drop(flash); - unsafe { bl.load(start) } + let flash = Mutex::new(RefCell::new(layout.bank1_region)); + + let config = BootLoaderConfig::from_linkerfile_blocking(&flash); + let active_offset = config.active.offset(); + let mut bl: BootLoader<_, _, _, 2048> = BootLoader::new(config); + + bl.prepare(); + + unsafe { bl.load(BANK1_REGION.base + active_offset) } } #[no_mangle] From b527cc98af97e43186a9676b78b4b7f7ece1fa86 Mon Sep 17 00:00:00 2001 From: Rasmus Melchior Jacobsen Date: Tue, 30 May 2023 14:05:38 +0200 Subject: [PATCH 170/178] Formatting --- examples/boot/application/rp/src/bin/a.rs | 9 ++++----- examples/boot/application/stm32f3/src/bin/a.rs | 2 +- examples/boot/application/stm32f7/src/bin/a.rs | 4 +--- examples/boot/application/stm32h7/src/bin/a.rs | 4 +--- 4 files changed, 7 insertions(+), 12 deletions(-) diff --git a/examples/boot/application/rp/src/bin/a.rs b/examples/boot/application/rp/src/bin/a.rs index 3fa908b6..69850069 100644 --- a/examples/boot/application/rp/src/bin/a.rs +++ b/examples/boot/application/rp/src/bin/a.rs @@ -2,15 +2,16 @@ #![no_main] #![feature(type_alias_impl_trait)] +use core::cell::RefCell; + use defmt_rtt as _; use embassy_boot_rp::*; use embassy_executor::Spawner; use embassy_rp::flash::Flash; use embassy_rp::gpio::{Level, Output}; use embassy_rp::watchdog::Watchdog; -use embassy_time::{Duration, Timer}; use embassy_sync::blocking_mutex::Mutex; -use core::cell::RefCell; +use embassy_time::{Duration, Timer}; use embedded_storage::nor_flash::NorFlash; #[cfg(feature = "panic-probe")] use panic_probe as _; @@ -49,9 +50,7 @@ async fn main(_s: Spawner) { for chunk in APP_B.chunks(4096) { buf.0[..chunk.len()].copy_from_slice(chunk); defmt::info!("writing block at offset {}", offset); - writer - .write(offset, &buf.0[..]) - .unwrap(); + writer.write(offset, &buf.0[..]).unwrap(); offset += chunk.len() as u32; } watchdog.feed(); diff --git a/examples/boot/application/stm32f3/src/bin/a.rs b/examples/boot/application/stm32f3/src/bin/a.rs index 6a5c276f..c94676f0 100644 --- a/examples/boot/application/stm32f3/src/bin/a.rs +++ b/examples/boot/application/stm32f3/src/bin/a.rs @@ -5,12 +5,12 @@ #[cfg(feature = "defmt-rtt")] use defmt_rtt::*; use embassy_boot_stm32::{AlignedBuffer, FirmwareUpdater, FirmwareUpdaterConfig}; -use embassy_sync::mutex::Mutex; use embassy_embedded_hal::adapter::BlockingAsync; use embassy_executor::Spawner; use embassy_stm32::exti::ExtiInput; use embassy_stm32::flash::{Flash, WRITE_SIZE}; use embassy_stm32::gpio::{Input, Level, Output, Pull, Speed}; +use embassy_sync::mutex::Mutex; use panic_reset as _; static APP_B: &[u8] = include_bytes!("../../b.bin"); diff --git a/examples/boot/application/stm32f7/src/bin/a.rs b/examples/boot/application/stm32f7/src/bin/a.rs index 530683ca..fc2702c9 100644 --- a/examples/boot/application/stm32f7/src/bin/a.rs +++ b/examples/boot/application/stm32f7/src/bin/a.rs @@ -33,9 +33,7 @@ async fn main(_spawner: Spawner) { let mut buf = AlignedBuffer([0; 4096]); for chunk in APP_B.chunks(4096) { buf.as_mut()[..chunk.len()].copy_from_slice(chunk); - writer - .write(offset, buf.as_ref()) - .unwrap(); + writer.write(offset, buf.as_ref()).unwrap(); offset += chunk.len(); } let mut magic = AlignedBuffer([0; WRITE_SIZE]); diff --git a/examples/boot/application/stm32h7/src/bin/a.rs b/examples/boot/application/stm32h7/src/bin/a.rs index e5f94310..1a54464d 100644 --- a/examples/boot/application/stm32h7/src/bin/a.rs +++ b/examples/boot/application/stm32h7/src/bin/a.rs @@ -33,9 +33,7 @@ async fn main(_spawner: Spawner) { let mut buf = AlignedBuffer([0; 4096]); for chunk in APP_B.chunks(4096) { buf.as_mut()[..chunk.len()].copy_from_slice(chunk); - writer - .write(offset, buf.as_ref()) - .unwrap(); + writer.write(offset, buf.as_ref()).unwrap(); offset += chunk.len(); } let mut magic = AlignedBuffer([0; WRITE_SIZE]); From b703db4c09d1d4f0c4296a7fd7b808d96f29e1a2 Mon Sep 17 00:00:00 2001 From: Rasmus Melchior Jacobsen Date: Tue, 30 May 2023 14:07:35 +0200 Subject: [PATCH 171/178] Fix verify test --- embassy-boot/boot/src/lib.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/embassy-boot/boot/src/lib.rs b/embassy-boot/boot/src/lib.rs index d13eafdd..45a87bd0 100644 --- a/embassy-boot/boot/src/lib.rs +++ b/embassy-boot/boot/src/lib.rs @@ -287,7 +287,10 @@ mod tests { // On with the test let flash = flash.into_async(); - let mut updater = FirmwareUpdater::new(flash.dfu(), flash.state()); + let mut updater = FirmwareUpdater::new(FirmwareUpdaterConfig { + dfu: flash.dfu(), + state: flash.state(), + }); let mut aligned = [0; 4]; From c22d2b5b5bbc5e3c7d3a039e90b50d39809a10f2 Mon Sep 17 00:00:00 2001 From: Rasmus Melchior Jacobsen Date: Tue, 30 May 2023 14:13:53 +0200 Subject: [PATCH 172/178] Remove unused use's --- embassy-boot/boot/src/firmware_updater/asynch.rs | 8 ++++---- embassy-boot/boot/src/firmware_updater/blocking.rs | 12 ++++++------ 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/embassy-boot/boot/src/firmware_updater/asynch.rs b/embassy-boot/boot/src/firmware_updater/asynch.rs index d0780bdf..0b3f8831 100644 --- a/embassy-boot/boot/src/firmware_updater/asynch.rs +++ b/embassy-boot/boot/src/firmware_updater/asynch.rs @@ -1,5 +1,7 @@ use digest::Digest; +#[cfg(target_os = "none")] use embassy_embedded_hal::flash::partition::Partition; +#[cfg(target_os = "none")] use embassy_sync::blocking_mutex::raw::NoopRawMutex; use embedded_storage_async::nor_flash::NorFlash; @@ -13,14 +15,12 @@ pub struct FirmwareUpdater { state: STATE, } +#[cfg(target_os = "none")] impl<'a, FLASH: NorFlash> FirmwareUpdaterConfig, Partition<'a, NoopRawMutex, FLASH>> { /// Create a firmware updater config from the flash and address symbols defined in the linkerfile - #[cfg(target_os = "none")] - pub fn from_linkerfile(flash: &'a Mutex) -> Self { - use embassy_sync::mutex::Mutex; - + pub fn from_linkerfile(flash: &'a embassy_sync::mutex::Mutex) -> Self { extern "C" { static __bootloader_state_start: u32; static __bootloader_state_end: u32; diff --git a/embassy-boot/boot/src/firmware_updater/blocking.rs b/embassy-boot/boot/src/firmware_updater/blocking.rs index c4412614..551150c4 100644 --- a/embassy-boot/boot/src/firmware_updater/blocking.rs +++ b/embassy-boot/boot/src/firmware_updater/blocking.rs @@ -1,5 +1,7 @@ use digest::Digest; +#[cfg(target_os = "none")] use embassy_embedded_hal::flash::partition::BlockingPartition; +#[cfg(target_os = "none")] use embassy_sync::blocking_mutex::raw::NoopRawMutex; use embedded_storage::nor_flash::NorFlash; @@ -13,16 +15,14 @@ pub struct BlockingFirmwareUpdater { state: STATE, } +#[cfg(target_os = "none")] impl<'a, FLASH: NorFlash> FirmwareUpdaterConfig, BlockingPartition<'a, NoopRawMutex, FLASH>> { /// Create a firmware updater config from the flash and address symbols defined in the linkerfile - #[cfg(target_os = "none")] - pub fn from_linkerfile_blocking(flash: &'a Mutex>) -> Self { - use core::cell::RefCell; - - use embassy_sync::blocking_mutex::Mutex; - + pub fn from_linkerfile_blocking( + flash: &'a embassy_sync::blocking_mutex::Mutex>, + ) -> Self { extern "C" { static __bootloader_state_start: u32; static __bootloader_state_end: u32; From 36bd6c817ecd4bbe48d740d91c24473aae16c0fb Mon Sep 17 00:00:00 2001 From: George Elliott-Hunter Date: Tue, 30 May 2023 20:27:06 +0200 Subject: [PATCH 173/178] Add [profile.release] debug = true to all examples --- examples/.cargo/config.toml | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 examples/.cargo/config.toml diff --git a/examples/.cargo/config.toml b/examples/.cargo/config.toml new file mode 100644 index 00000000..84d26632 --- /dev/null +++ b/examples/.cargo/config.toml @@ -0,0 +1,3 @@ +[profile.release] +# Allows defmt to display log locations even in release +debug = true \ No newline at end of file From c327c6cd6fc3c11cfaf83cf64591940d401c5f6b Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Tue, 30 May 2023 22:42:49 +0200 Subject: [PATCH 174/178] cyw43: move crate to subdir. --- .github/workflows/rust.yml | 29 - .gitignore | 2 - .vscode/extensions.json | 11 - .vscode/settings.json | 17 - LICENSE-APACHE | 201 -- LICENSE-MIT | 25 - ci.sh | 25 - {firmware => cyw43-firmware}/43439A0.bin | Bin {firmware => cyw43-firmware}/43439A0_clm.bin | Bin .../LICENSE-permissive-binary-license-1.0.txt | 0 {firmware => cyw43-firmware}/README.md | 0 cyw43-pio/Cargo.toml | 2 +- Cargo.toml => cyw43/Cargo.toml | 0 README.md => cyw43/README.md | 0 {src => cyw43/src}/bus.rs | 0 {src => cyw43/src}/consts.rs | 0 {src => cyw43/src}/control.rs | 0 {src => cyw43/src}/countries.rs | 0 {src => cyw43/src}/events.rs | 0 {src => cyw43/src}/fmt.rs | 0 {src => cyw43/src}/ioctl.rs | 0 {src => cyw43/src}/lib.rs | 0 {src => cyw43/src}/nvram.rs | 0 {src => cyw43/src}/runner.rs | 0 {src => cyw43/src}/structs.rs | 0 examples/rpi-pico-w/Cargo.lock | 1707 +++++++++++++++++ examples/rpi-pico-w/Cargo.toml | 2 +- examples/rpi-pico-w/src/bin/tcp_server.rs | 25 +- examples/rpi-pico-w/src/bin/tcp_server_ap.rs | 20 +- examples/rpi-pico-w/src/bin/wifi_scan.rs | 20 +- rust-toolchain.toml | 8 - rustfmt.toml | 3 - 32 files changed, 1761 insertions(+), 336 deletions(-) delete mode 100644 .github/workflows/rust.yml delete mode 100644 .gitignore delete mode 100644 .vscode/extensions.json delete mode 100644 .vscode/settings.json delete mode 100644 LICENSE-APACHE delete mode 100644 LICENSE-MIT delete mode 100755 ci.sh rename {firmware => cyw43-firmware}/43439A0.bin (100%) rename {firmware => cyw43-firmware}/43439A0_clm.bin (100%) rename {firmware => cyw43-firmware}/LICENSE-permissive-binary-license-1.0.txt (100%) rename {firmware => cyw43-firmware}/README.md (100%) rename Cargo.toml => cyw43/Cargo.toml (100%) rename README.md => cyw43/README.md (100%) rename {src => cyw43/src}/bus.rs (100%) rename {src => cyw43/src}/consts.rs (100%) rename {src => cyw43/src}/control.rs (100%) rename {src => cyw43/src}/countries.rs (100%) rename {src => cyw43/src}/events.rs (100%) rename {src => cyw43/src}/fmt.rs (100%) rename {src => cyw43/src}/ioctl.rs (100%) rename {src => cyw43/src}/lib.rs (100%) rename {src => cyw43/src}/nvram.rs (100%) rename {src => cyw43/src}/runner.rs (100%) rename {src => cyw43/src}/structs.rs (100%) create mode 100644 examples/rpi-pico-w/Cargo.lock delete mode 100644 rust-toolchain.toml delete mode 100644 rustfmt.toml diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml deleted file mode 100644 index 2cd3ba5d..00000000 --- a/.github/workflows/rust.yml +++ /dev/null @@ -1,29 +0,0 @@ -name: Rust - -on: - push: - branches: [master] - pull_request: - branches: [master] - merge_group: - -env: - CARGO_TERM_COLOR: always - -jobs: - build-nightly: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - uses: actions/cache@v2 - with: - path: | - ~/.cargo/registry - ~/.cargo/git - target - key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} - - - name: Check fmt - run: cargo fmt -- --check - - name: Build - run: ./ci.sh diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 1e7caa9e..00000000 --- a/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -Cargo.lock -target/ diff --git a/.vscode/extensions.json b/.vscode/extensions.json deleted file mode 100644 index a8bb78ad..00000000 --- a/.vscode/extensions.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - // See https://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations. - // Extension identifier format: ${publisher}.${name}. Example: vscode.csharp - // List of extensions which should be recommended for users of this workspace. - "recommendations": [ - "rust-lang.rust-analyzer", - "tamasfe.even-better-toml", - ], - // List of extensions recommended by VS Code that should not be recommended for users of this workspace. - "unwantedRecommendations": [] -} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index dd479929..00000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "editor.formatOnSave": true, - "[toml]": { - "editor.formatOnSave": false - }, - "rust-analyzer.cargo.target": "thumbv6m-none-eabi", - "rust-analyzer.cargo.noDefaultFeatures": true, - "rust-analyzer.check.allTargets": false, - "rust-analyzer.check.noDefaultFeatures": true, - "rust-analyzer.linkedProjects": [ - "examples/rpi-pico-w/Cargo.toml", - ], - "rust-analyzer.server.extraEnv": { - "WIFI_NETWORK": "foo", - "WIFI_PASSWORD": "foo", - } -} \ No newline at end of file diff --git a/LICENSE-APACHE b/LICENSE-APACHE deleted file mode 100644 index ea4fa15c..00000000 --- a/LICENSE-APACHE +++ /dev/null @@ -1,201 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - -TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - -1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - -2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - -3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - -4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - -5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - -6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - -7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - -8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - -9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - -END OF TERMS AND CONDITIONS - -APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - -Copyright 2019-2022 Embassy project contributors - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. diff --git a/LICENSE-MIT b/LICENSE-MIT deleted file mode 100644 index 87c05283..00000000 --- a/LICENSE-MIT +++ /dev/null @@ -1,25 +0,0 @@ -Copyright (c) 2019-2022 Embassy project contributors - -Permission is hereby granted, free of charge, to any -person obtaining a copy of this software and associated -documentation files (the "Software"), to deal in the -Software without restriction, including without -limitation the rights to use, copy, modify, merge, -publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software -is furnished to do so, subject to the following -conditions: - -The above copyright notice and this permission notice -shall be included in all copies or substantial portions -of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF -ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED -TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A -PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT -SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR -IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -DEALINGS IN THE SOFTWARE. diff --git a/ci.sh b/ci.sh deleted file mode 100755 index 91683820..00000000 --- a/ci.sh +++ /dev/null @@ -1,25 +0,0 @@ -#!/bin/bash - -set -euxo pipefail -cd $(dirname $0) - -export CARGO_TARGET_DIR=$(pwd)/target -export DEFMT_LOG=trace - -# build examples -#================== - -(cd examples/rpi-pico-w; WIFI_NETWORK=foo WIFI_PASSWORD=bar cargo build --release) - - -# build with log/defmt combinations -#===================================== - -cargo build --target thumbv6m-none-eabi --features '' -cargo build --target thumbv6m-none-eabi --features 'log' -cargo build --target thumbv6m-none-eabi --features 'defmt' -cargo build --target thumbv6m-none-eabi --features 'log,firmware-logs' -cargo build --target thumbv6m-none-eabi --features 'defmt,firmware-logs' - -(cd cyw43-pio; cargo build --target thumbv6m-none-eabi --features '') -(cd cyw43-pio; cargo build --target thumbv6m-none-eabi --features 'overclock') diff --git a/firmware/43439A0.bin b/cyw43-firmware/43439A0.bin similarity index 100% rename from firmware/43439A0.bin rename to cyw43-firmware/43439A0.bin diff --git a/firmware/43439A0_clm.bin b/cyw43-firmware/43439A0_clm.bin similarity index 100% rename from firmware/43439A0_clm.bin rename to cyw43-firmware/43439A0_clm.bin diff --git a/firmware/LICENSE-permissive-binary-license-1.0.txt b/cyw43-firmware/LICENSE-permissive-binary-license-1.0.txt similarity index 100% rename from firmware/LICENSE-permissive-binary-license-1.0.txt rename to cyw43-firmware/LICENSE-permissive-binary-license-1.0.txt diff --git a/firmware/README.md b/cyw43-firmware/README.md similarity index 100% rename from firmware/README.md rename to cyw43-firmware/README.md diff --git a/cyw43-pio/Cargo.toml b/cyw43-pio/Cargo.toml index 2238f761..a7af2c8e 100644 --- a/cyw43-pio/Cargo.toml +++ b/cyw43-pio/Cargo.toml @@ -9,7 +9,7 @@ edition = "2021" overclock = [] [dependencies] -cyw43 = { path = "../" } +cyw43 = { path = "../cyw43" } embassy-rp = { version = "0.1.0", features = ["unstable-traits", "nightly", "unstable-pac", "time-driver"] } pio-proc = "0.2" pio = "0.2.1" diff --git a/Cargo.toml b/cyw43/Cargo.toml similarity index 100% rename from Cargo.toml rename to cyw43/Cargo.toml diff --git a/README.md b/cyw43/README.md similarity index 100% rename from README.md rename to cyw43/README.md diff --git a/src/bus.rs b/cyw43/src/bus.rs similarity index 100% rename from src/bus.rs rename to cyw43/src/bus.rs diff --git a/src/consts.rs b/cyw43/src/consts.rs similarity index 100% rename from src/consts.rs rename to cyw43/src/consts.rs diff --git a/src/control.rs b/cyw43/src/control.rs similarity index 100% rename from src/control.rs rename to cyw43/src/control.rs diff --git a/src/countries.rs b/cyw43/src/countries.rs similarity index 100% rename from src/countries.rs rename to cyw43/src/countries.rs diff --git a/src/events.rs b/cyw43/src/events.rs similarity index 100% rename from src/events.rs rename to cyw43/src/events.rs diff --git a/src/fmt.rs b/cyw43/src/fmt.rs similarity index 100% rename from src/fmt.rs rename to cyw43/src/fmt.rs diff --git a/src/ioctl.rs b/cyw43/src/ioctl.rs similarity index 100% rename from src/ioctl.rs rename to cyw43/src/ioctl.rs diff --git a/src/lib.rs b/cyw43/src/lib.rs similarity index 100% rename from src/lib.rs rename to cyw43/src/lib.rs diff --git a/src/nvram.rs b/cyw43/src/nvram.rs similarity index 100% rename from src/nvram.rs rename to cyw43/src/nvram.rs diff --git a/src/runner.rs b/cyw43/src/runner.rs similarity index 100% rename from src/runner.rs rename to cyw43/src/runner.rs diff --git a/src/structs.rs b/cyw43/src/structs.rs similarity index 100% rename from src/structs.rs rename to cyw43/src/structs.rs diff --git a/examples/rpi-pico-w/Cargo.lock b/examples/rpi-pico-w/Cargo.lock new file mode 100644 index 00000000..16095835 --- /dev/null +++ b/examples/rpi-pico-w/Cargo.lock @@ -0,0 +1,1707 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "aho-corasick" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67fc08ce920c31afb70f013dcce1bfc3a3195de6a228474e45e1f145b36f8d04" +dependencies = [ + "memchr", +] + +[[package]] +name = "arrayvec" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6" + +[[package]] +name = "as-slice" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45403b49e3954a4b8428a0ac21a4b7afadccf92bfd96273f1a58cd4812496ae0" +dependencies = [ + "generic-array 0.12.4", + "generic-array 0.13.3", + "generic-array 0.14.7", + "stable_deref_trait", +] + +[[package]] +name = "as-slice" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "516b6b4f0e40d50dcda9365d53964ec74560ad4284da2e7fc97122cd83174516" +dependencies = [ + "stable_deref_trait", +] + +[[package]] +name = "ascii-canvas" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8824ecca2e851cec16968d54a01dd372ef8f95b244fb84b84e70128be347c3c6" +dependencies = [ + "term", +] + +[[package]] +name = "atomic-polyfill" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3ff7eb3f316534d83a8a2c3d1674ace8a5a71198eba31e2e2b597833f699b28" +dependencies = [ + "critical-section 1.1.1", +] + +[[package]] +name = "atomic-polyfill" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c314e70d181aa6053b26e3f7fbf86d1dfff84f816a6175b967666b3506ef7289" +dependencies = [ + "critical-section 1.1.1", +] + +[[package]] +name = "atomic-pool" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58c5fc22e05ec2884db458bf307dc7b278c9428888d2b6e6fad9c0ae7804f5f6" +dependencies = [ + "as-slice 0.1.5", + "as-slice 0.2.1", + "atomic-polyfill 1.0.2", + "stable_deref_trait", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "az" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b7e4c2464d97fe331d41de9d5db0def0a96f4d823b8b32a2efd503578988973" + +[[package]] +name = "bare-metal" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5deb64efa5bd81e31fcd1938615a6d98c82eafcbcd787162b6f63b91d6bac5b3" +dependencies = [ + "rustc_version 0.2.3", +] + +[[package]] +name = "bare-metal" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8fe8f5a8a398345e52358e18ff07cc17a568fbca5c6f73873d3a62056309603" + +[[package]] +name = "bit-set" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" + +[[package]] +name = "bitfield" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46afbd2983a5d5a7bd740ccb198caf5b82f45c40c09c0eed36052d91cb92e719" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bytemuck" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17febce684fd15d89027105661fec94afb475cb995fbc59d2865198446ba2eea" + +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + +[[package]] +name = "cc" +version = "1.0.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "codespan-reporting" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" +dependencies = [ + "termcolor", + "unicode-width", +] + +[[package]] +name = "cortex-m" +version = "0.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ec610d8f49840a5b376c69663b6369e71f4b34484b9b2eb29fb918d92516cb9" +dependencies = [ + "bare-metal 0.2.5", + "bitfield", + "critical-section 1.1.1", + "embedded-hal 0.2.7", + "volatile-register", +] + +[[package]] +name = "cortex-m-rt" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee84e813d593101b1723e13ec38b6ab6abbdbaaa4546553f5395ed274079ddb1" +dependencies = [ + "cortex-m-rt-macros", +] + +[[package]] +name = "cortex-m-rt-macros" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f6f3e36f203cfedbc78b357fb28730aa2c6dc1ab060ee5c2405e843988d3c7" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "crc-any" +version = "2.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "774646b687f63643eb0f4bf13dc263cb581c8c9e57973b6ddf78bda3994d88df" +dependencies = [ + "debug-helper", +] + +[[package]] +name = "critical-section" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1706d332edc22aef4d9f23a6bb1c92360a403013c291af51247a737472dcae6" +dependencies = [ + "bare-metal 1.0.0", + "critical-section 1.1.1", +] + +[[package]] +name = "critical-section" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6548a0ad5d2549e111e1f6a11a6c2e2d00ce6a3dafe22948d67c2b443f775e52" + +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + +[[package]] +name = "cyw43" +version = "0.1.0" +dependencies = [ + "atomic-polyfill 0.1.11", + "cortex-m", + "cortex-m-rt", + "defmt", + "embassy-futures", + "embassy-net-driver-channel", + "embassy-sync", + "embassy-time", + "embedded-hal 1.0.0-alpha.10", + "futures", + "num_enum", +] + +[[package]] +name = "cyw43-example-rpi-pico-w" +version = "0.1.0" +dependencies = [ + "atomic-polyfill 0.1.11", + "cortex-m", + "cortex-m-rt", + "cyw43", + "cyw43-pio", + "defmt", + "defmt-rtt", + "embassy-executor", + "embassy-net", + "embassy-rp", + "embassy-time", + "embedded-io", + "futures", + "heapless", + "panic-probe", + "static_cell", +] + +[[package]] +name = "cyw43-pio" +version = "0.1.0" +dependencies = [ + "cyw43", + "defmt", + "embassy-rp", + "fixed", + "pio", + "pio-proc", +] + +[[package]] +name = "darling" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a01d95850c592940db9b8194bc39f4bc0e89dee5c4265e4b1807c34a9aba453c" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "859d65a907b6852c9361e3185c862aae7fafd2887876799fa55f5f99dc40d610" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 1.0.109", +] + +[[package]] +name = "darling_macro" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c972679f83bdf9c42bd905396b6c3588a843a17f0f16dfcfa3e2c5d57441835" +dependencies = [ + "darling_core", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "debug-helper" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f578e8e2c440e7297e008bb5486a3a8a194775224bbc23729b0dbdfaeebf162e" + +[[package]] +name = "defmt" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "956673bd3cb347512bf988d1e8d89ac9a82b64f6eec54d3c01c3529dac019882" +dependencies = [ + "bitflags", + "defmt-macros", +] + +[[package]] +name = "defmt-macros" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4abc4821bd84d3d8f49945ddb24d029be9385ed9b77c99bf2f6296847a6a9f0" +dependencies = [ + "defmt-parser", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "defmt-parser" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "269924c02afd7f94bc4cecbfa5c379f6ffcf9766b3408fe63d22c728654eccd0" +dependencies = [ + "thiserror", +] + +[[package]] +name = "defmt-rtt" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d2cbbbd58847d508d97629b32cd9730a2d28532f71e219714614406029f18b1" +dependencies = [ + "critical-section 0.2.8", + "defmt", +] + +[[package]] +name = "diff" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" + +[[package]] +name = "dirs-next" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" +dependencies = [ + "cfg-if", + "dirs-sys-next", +] + +[[package]] +name = "dirs-sys-next" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + +[[package]] +name = "either" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" + +[[package]] +name = "embassy-cortex-m" +version = "0.1.0" +source = "git+https://github.com/embassy-rs/embassy?rev=82f7e104d90a6628d1873017ea5ef6a7afb3b3f7#82f7e104d90a6628d1873017ea5ef6a7afb3b3f7" +dependencies = [ + "atomic-polyfill 1.0.2", + "cfg-if", + "cortex-m", + "critical-section 1.1.1", + "embassy-executor", + "embassy-hal-common", + "embassy-macros", + "embassy-sync", +] + +[[package]] +name = "embassy-embedded-hal" +version = "0.1.0" +source = "git+https://github.com/embassy-rs/embassy?rev=82f7e104d90a6628d1873017ea5ef6a7afb3b3f7#82f7e104d90a6628d1873017ea5ef6a7afb3b3f7" +dependencies = [ + "embassy-sync", + "embedded-hal 0.2.7", + "embedded-hal 1.0.0-alpha.10", + "embedded-hal-async", + "embedded-storage", + "embedded-storage-async", + "nb 1.1.0", +] + +[[package]] +name = "embassy-executor" +version = "0.2.0" +source = "git+https://github.com/embassy-rs/embassy?rev=82f7e104d90a6628d1873017ea5ef6a7afb3b3f7#82f7e104d90a6628d1873017ea5ef6a7afb3b3f7" +dependencies = [ + "atomic-polyfill 1.0.2", + "cortex-m", + "critical-section 1.1.1", + "defmt", + "embassy-macros", + "embassy-time", + "futures-util", + "static_cell", +] + +[[package]] +name = "embassy-futures" +version = "0.1.0" +source = "git+https://github.com/embassy-rs/embassy?rev=82f7e104d90a6628d1873017ea5ef6a7afb3b3f7#82f7e104d90a6628d1873017ea5ef6a7afb3b3f7" + +[[package]] +name = "embassy-hal-common" +version = "0.1.0" +source = "git+https://github.com/embassy-rs/embassy?rev=82f7e104d90a6628d1873017ea5ef6a7afb3b3f7#82f7e104d90a6628d1873017ea5ef6a7afb3b3f7" +dependencies = [ + "defmt", + "num-traits", +] + +[[package]] +name = "embassy-macros" +version = "0.2.0" +source = "git+https://github.com/embassy-rs/embassy?rev=82f7e104d90a6628d1873017ea5ef6a7afb3b3f7#82f7e104d90a6628d1873017ea5ef6a7afb3b3f7" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "embassy-net" +version = "0.1.0" +source = "git+https://github.com/embassy-rs/embassy?rev=82f7e104d90a6628d1873017ea5ef6a7afb3b3f7#82f7e104d90a6628d1873017ea5ef6a7afb3b3f7" +dependencies = [ + "as-slice 0.2.1", + "atomic-polyfill 1.0.2", + "atomic-pool", + "defmt", + "embassy-hal-common", + "embassy-net-driver", + "embassy-sync", + "embassy-time", + "embedded-io", + "embedded-nal-async", + "futures", + "generic-array 0.14.7", + "heapless", + "managed", + "smoltcp", + "stable_deref_trait", +] + +[[package]] +name = "embassy-net-driver" +version = "0.1.0" +source = "git+https://github.com/embassy-rs/embassy?rev=82f7e104d90a6628d1873017ea5ef6a7afb3b3f7#82f7e104d90a6628d1873017ea5ef6a7afb3b3f7" +dependencies = [ + "defmt", +] + +[[package]] +name = "embassy-net-driver-channel" +version = "0.1.0" +source = "git+https://github.com/embassy-rs/embassy?rev=82f7e104d90a6628d1873017ea5ef6a7afb3b3f7#82f7e104d90a6628d1873017ea5ef6a7afb3b3f7" +dependencies = [ + "embassy-futures", + "embassy-net-driver", + "embassy-sync", +] + +[[package]] +name = "embassy-rp" +version = "0.1.0" +source = "git+https://github.com/embassy-rs/embassy?rev=82f7e104d90a6628d1873017ea5ef6a7afb3b3f7#82f7e104d90a6628d1873017ea5ef6a7afb3b3f7" +dependencies = [ + "atomic-polyfill 1.0.2", + "cfg-if", + "cortex-m", + "cortex-m-rt", + "critical-section 1.1.1", + "defmt", + "embassy-cortex-m", + "embassy-embedded-hal", + "embassy-executor", + "embassy-futures", + "embassy-hal-common", + "embassy-sync", + "embassy-time", + "embassy-usb-driver", + "embedded-hal 0.2.7", + "embedded-hal 1.0.0-alpha.10", + "embedded-hal-async", + "embedded-hal-nb", + "embedded-io", + "embedded-storage", + "fixed", + "futures", + "nb 1.1.0", + "paste", + "pio", + "pio-proc", + "rand_core", + "rp-pac", + "rp2040-boot2", +] + +[[package]] +name = "embassy-sync" +version = "0.2.0" +source = "git+https://github.com/embassy-rs/embassy?rev=82f7e104d90a6628d1873017ea5ef6a7afb3b3f7#82f7e104d90a6628d1873017ea5ef6a7afb3b3f7" +dependencies = [ + "cfg-if", + "critical-section 1.1.1", + "embedded-io", + "futures-util", + "heapless", +] + +[[package]] +name = "embassy-time" +version = "0.1.1" +source = "git+https://github.com/embassy-rs/embassy?rev=82f7e104d90a6628d1873017ea5ef6a7afb3b3f7#82f7e104d90a6628d1873017ea5ef6a7afb3b3f7" +dependencies = [ + "atomic-polyfill 1.0.2", + "cfg-if", + "critical-section 1.1.1", + "defmt", + "embedded-hal 0.2.7", + "futures-util", + "heapless", +] + +[[package]] +name = "embassy-usb-driver" +version = "0.1.0" +source = "git+https://github.com/embassy-rs/embassy?rev=82f7e104d90a6628d1873017ea5ef6a7afb3b3f7#82f7e104d90a6628d1873017ea5ef6a7afb3b3f7" +dependencies = [ + "defmt", +] + +[[package]] +name = "embedded-hal" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35949884794ad573cf46071e41c9b60efb0cb311e3ca01f7af807af1debc66ff" +dependencies = [ + "nb 0.1.3", + "void", +] + +[[package]] +name = "embedded-hal" +version = "1.0.0-alpha.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f65c4d073f5d91c66e629b216818a4c9747eeda0debedf2deda9a0a947e4e93b" + +[[package]] +name = "embedded-hal-async" +version = "0.2.0-alpha.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8042370aa7af48de36d5312cda14c18ed8ca6b7ce64f5a07832fedc9dc83063f" +dependencies = [ + "embedded-hal 1.0.0-alpha.10", +] + +[[package]] +name = "embedded-hal-nb" +version = "1.0.0-alpha.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1465fffd56a95bbc105c17965bca1c1d5815027b1cc6bb183bc05d04563d065c" +dependencies = [ + "embedded-hal 1.0.0-alpha.10", + "nb 1.1.0", +] + +[[package]] +name = "embedded-io" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef1a6892d9eef45c8fa6b9e0086428a2cca8491aca8f787c534a3d6d0bcb3ced" +dependencies = [ + "defmt", +] + +[[package]] +name = "embedded-nal" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db9efecb57ab54fa918730f2874d7d37647169c50fa1357fecb81abee840b113" +dependencies = [ + "heapless", + "nb 1.1.0", + "no-std-net 0.5.0", +] + +[[package]] +name = "embedded-nal-async" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27ce84f518ca912777ec143db235f4d615e3bf8d4e46d507d6ef12daf5b1df98" +dependencies = [ + "embedded-io", + "embedded-nal", + "heapless", + "no-std-net 0.6.0", +] + +[[package]] +name = "embedded-storage" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "156d7a2fdd98ebbf9ae579cbceca3058cff946e13f8e17b90e3511db0508c723" + +[[package]] +name = "embedded-storage-async" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "052997a894670d0cde873faa7405bc98e2fd29f569d2acd568561bc1c396b35a" +dependencies = [ + "embedded-storage", +] + +[[package]] +name = "ena" +version = "0.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c533630cf40e9caa44bd91aadc88a75d75a4c3a12b4cfde353cbed41daa1e1f1" +dependencies = [ + "log", +] + +[[package]] +name = "errno" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" +dependencies = [ + "errno-dragonfly", + "libc", + "windows-sys 0.48.0", +] + +[[package]] +name = "errno-dragonfly" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "fixed" +version = "1.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79386fdcec5e0fde91b1a6a5bcd89677d1f9304f7f986b154a1b9109038854d9" +dependencies = [ + "az", + "bytemuck", + "half", + "typenum", +] + +[[package]] +name = "fixedbitset" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "futures" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" + +[[package]] +name = "futures-io" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" + +[[package]] +name = "futures-macro" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.18", +] + +[[package]] +name = "futures-sink" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" + +[[package]] +name = "futures-task" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" + +[[package]] +name = "futures-util" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" +dependencies = [ + "futures-core", + "futures-macro", + "futures-sink", + "futures-task", + "pin-project-lite", + "pin-utils", +] + +[[package]] +name = "generic-array" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffdf9f34f1447443d37393cc6c2b8313aebddcd96906caf34e54c68d8e57d7bd" +dependencies = [ + "typenum", +] + +[[package]] +name = "generic-array" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f797e67af32588215eaaab8327027ee8e71b9dd0b2b26996aedf20c030fce309" +dependencies = [ + "typenum", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c85e1d9ab2eadba7e5040d4e09cbd6d072b76a557ad64e797c2cb9d4da21d7e4" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "half" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02b4af3693f1b705df946e9fe5631932443781d0aabb423b62fcd4d73f6d2fd0" +dependencies = [ + "crunchy", +] + +[[package]] +name = "hash32" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0c35f58762feb77d74ebe43bdbc3210f09be9fe6742234d573bacc26ed92b67" +dependencies = [ + "byteorder", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "heapless" +version = "0.7.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db04bc24a18b9ea980628ecf00e6c0264f3c1426dac36c00cb49b6fbad8b0743" +dependencies = [ + "atomic-polyfill 0.1.11", + "defmt", + "hash32", + "rustc_version 0.4.0", + "spin", + "stable_deref_trait", +] + +[[package]] +name = "hermit-abi" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown", +] + +[[package]] +name = "io-lifetimes" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" +dependencies = [ + "hermit-abi", + "libc", + "windows-sys 0.48.0", +] + +[[package]] +name = "is-terminal" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adcf93614601c8129ddf72e2d5633df827ba6551541c6d8c59520a371475be1f" +dependencies = [ + "hermit-abi", + "io-lifetimes", + "rustix", + "windows-sys 0.48.0", +] + +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + +[[package]] +name = "lalrpop" +version = "0.19.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a1cbf952127589f2851ab2046af368fd20645491bb4b376f04b7f94d7a9837b" +dependencies = [ + "ascii-canvas", + "bit-set", + "diff", + "ena", + "is-terminal", + "itertools", + "lalrpop-util", + "petgraph", + "regex", + "regex-syntax 0.6.29", + "string_cache", + "term", + "tiny-keccak", + "unicode-xid", +] + +[[package]] +name = "lalrpop-util" +version = "0.19.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3c48237b9604c5a4702de6b824e02006c3214327564636aef27c1028a8fa0ed" +dependencies = [ + "regex", +] + +[[package]] +name = "libc" +version = "0.2.144" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b00cc1c228a6782d0f076e7b232802e0c5689d41bb5df366f2a6b6621cfdfe1" + +[[package]] +name = "linux-raw-sys" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" + +[[package]] +name = "lock_api" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "518ef76f2f87365916b142844c16d8fefd85039bc5699050210a7778ee1cd1de" + +[[package]] +name = "managed" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ca88d725a0a943b096803bd34e73a4437208b6077654cc4ecb2947a5f91618d" + +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + +[[package]] +name = "nb" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "801d31da0513b6ec5214e9bf433a77966320625a37860f910be265be6e18d06f" +dependencies = [ + "nb 1.1.0", +] + +[[package]] +name = "nb" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d5439c4ad607c3c23abf66de8c8bf57ba8adcd1f129e699851a6e43935d339d" + +[[package]] +name = "new_debug_unreachable" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54" + +[[package]] +name = "no-std-net" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bcece43b12349917e096cddfa66107277f123e6c96a5aea78711dc601a47152" + +[[package]] +name = "no-std-net" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43794a0ace135be66a25d3ae77d41b91615fb68ae937f904090203e81f755b65" + +[[package]] +name = "num-traits" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_enum" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f646caf906c20226733ed5b1374287eb97e3c2a5c227ce668c1f2ce20ae57c9" +dependencies = [ + "num_enum_derive", +] + +[[package]] +name = "num_enum_derive" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcbff9bc912032c62bf65ef1d5aea88983b420f4f839db1e9b0c281a25c9c799" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "once_cell" +version = "1.17.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9670a07f94779e00908f3e686eab508878ebb390ba6e604d3a284c00e8d0487b" + +[[package]] +name = "panic-probe" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa6fa5645ef5a760cd340eaa92af9c1ce131c8c09e7f8926d8a24b59d26652b9" +dependencies = [ + "cortex-m", + "defmt", +] + +[[package]] +name = "parking_lot" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9069cbb9f99e3a5083476ccb29ceb1de18b9118cafa53e90c9551235de2b9521" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-sys 0.45.0", +] + +[[package]] +name = "paste" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f746c4065a8fa3fe23974dd82f15431cc8d40779821001404d10d2e79ca7d79" + +[[package]] +name = "petgraph" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dd7d28ee937e54fe3080c91faa1c3a46c06de6252988a7f4592ba2310ef22a4" +dependencies = [ + "fixedbitset", + "indexmap", +] + +[[package]] +name = "phf_shared" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096" +dependencies = [ + "siphasher", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pio" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76e09694b50f89f302ed531c1f2a7569f0be5867aee4ab4f8f729bbeec0078e3" +dependencies = [ + "arrayvec", + "num_enum", + "paste", +] + +[[package]] +name = "pio-parser" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77532c2b8279aef98dfc7207ef15298a5a3d6b6cc76ccc8b65913d69f3a8dd6b" +dependencies = [ + "lalrpop", + "lalrpop-util", + "pio", + "regex-syntax 0.6.29", +] + +[[package]] +name = "pio-proc" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b04dc870fb3a4fd8b3e4ca8c61b53bc8ac4eb78b66805d2b3c2e5c4829e0d7a" +dependencies = [ + "codespan-reporting", + "lalrpop-util", + "pio", + "pio-parser", + "proc-macro-error", + "proc-macro2", + "quote", + "regex-syntax 0.6.29", + "syn 1.0.109", +] + +[[package]] +name = "precomputed-hash" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn 1.0.109", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro2" +version = "1.0.59" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6aeca18b86b413c660b781aa319e4e2648a3e6f9eadc9b47e9038e6fe9f3451b" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9ab9c7eadfd8df19006f1cf1a4aed13540ed5cbc047010ece5826e10825488" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" + +[[package]] +name = "redox_syscall" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +dependencies = [ + "bitflags", +] + +[[package]] +name = "redox_users" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" +dependencies = [ + "getrandom", + "redox_syscall", + "thiserror", +] + +[[package]] +name = "regex" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81ca098a9821bd52d6b24fd8b10bd081f47d39c22778cafaa75a2857a62c6390" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax 0.7.2", +] + +[[package]] +name = "regex-syntax" +version = "0.6.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" + +[[package]] +name = "regex-syntax" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "436b050e76ed2903236f032a59761c1eb99e1b0aead2c257922771dab1fc8c78" + +[[package]] +name = "rp-pac" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a76e426cd8377db668fba1fe885028788b126b7cef91059cd478de8b076c2915" +dependencies = [ + "cortex-m", + "cortex-m-rt", +] + +[[package]] +name = "rp2040-boot2" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c92f344f63f950ee36cf4080050e4dce850839b9175da38f9d2ffb69b4dbb21" +dependencies = [ + "crc-any", +] + +[[package]] +name = "rustc_version" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" +dependencies = [ + "semver 0.9.0", +] + +[[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver 1.0.17", +] + +[[package]] +name = "rustix" +version = "0.37.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acf8729d8542766f1b2cf77eb034d52f40d375bb8b615d0b147089946e16613d" +dependencies = [ + "bitflags", + "errno", + "io-lifetimes", + "libc", + "linux-raw-sys", + "windows-sys 0.48.0", +] + +[[package]] +name = "rustversion" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f3208ce4d8448b3f3e7d168a73f5e0c43a61e32930de3bceeccedb388b6bf06" + +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + +[[package]] +name = "semver" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" +dependencies = [ + "semver-parser", +] + +[[package]] +name = "semver" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bebd363326d05ec3e2f532ab7660680f3b02130d780c299bca73469d521bc0ed" + +[[package]] +name = "semver-parser" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" + +[[package]] +name = "siphasher" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7bd3e3206899af3f8b12af284fafc038cc1dc2b41d1b89dd17297221c5d225de" + +[[package]] +name = "smallvec" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" + +[[package]] +name = "smoltcp" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e9786ac45091b96f946693e05bfa4d8ca93e2d3341237d97a380107a6b38dea" +dependencies = [ + "bitflags", + "byteorder", + "cfg-if", + "defmt", + "heapless", + "managed", +] + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +dependencies = [ + "lock_api", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + +[[package]] +name = "static_cell" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4c37c250d21f53fa7165e76e5401d7e6539c211a8d2cf449e3962956a5cc2ce" +dependencies = [ + "atomic-polyfill 1.0.2", +] + +[[package]] +name = "string_cache" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f91138e76242f575eb1d3b38b4f1362f10d3a43f47d182a5b359af488a02293b" +dependencies = [ + "new_debug_unreachable", + "once_cell", + "parking_lot", + "phf_shared", + "precomputed-hash", +] + +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32d41677bcbe24c20c52e7c70b0d8db04134c5d1066bf98662e2871ad200ea3e" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "term" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c59df8ac95d96ff9bede18eb7300b0fda5e5d8d90960e76f8e14ae765eedbf1f" +dependencies = [ + "dirs-next", + "rustversion", + "winapi", +] + +[[package]] +name = "termcolor" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "thiserror" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.18", +] + +[[package]] +name = "tiny-keccak" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] + +[[package]] +name = "typenum" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" + +[[package]] +name = "unicode-ident" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15811caf2415fb889178633e7724bad2509101cde276048e013b9def5e51fa0" + +[[package]] +name = "unicode-width" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" + +[[package]] +name = "unicode-xid" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" + +[[package]] +name = "vcell" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77439c1b53d2303b20d9459b1ade71a83c716e3f9c34f3228c00e6f185d6c002" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "void" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" + +[[package]] +name = "volatile-register" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ee8f19f9d74293faf70901bc20ad067dc1ad390d2cbf1e3f75f721ffee908b6" +dependencies = [ + "vcell", +] + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.0", +] + +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + +[[package]] +name = "windows-targets" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" +dependencies = [ + "windows_aarch64_gnullvm 0.48.0", + "windows_aarch64_msvc 0.48.0", + "windows_i686_gnu 0.48.0", + "windows_i686_msvc 0.48.0", + "windows_x86_64_gnu 0.48.0", + "windows_x86_64_gnullvm 0.48.0", + "windows_x86_64_msvc 0.48.0", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" diff --git a/examples/rpi-pico-w/Cargo.toml b/examples/rpi-pico-w/Cargo.toml index 525e934f..d3ce3085 100644 --- a/examples/rpi-pico-w/Cargo.toml +++ b/examples/rpi-pico-w/Cargo.toml @@ -5,7 +5,7 @@ edition = "2021" [dependencies] -cyw43 = { path = "../../", features = ["defmt", "firmware-logs"] } +cyw43 = { path = "../../cyw43", features = ["defmt", "firmware-logs"] } cyw43-pio = { path = "../../cyw43-pio", features = ["defmt", "overclock"] } embassy-executor = { version = "0.2.0", features = ["defmt", "integrated-timers", "executor-thread", "arch-cortex-m"] } embassy-time = { version = "0.1.0", features = ["defmt", "defmt-timestamp-uptime"] } diff --git a/examples/rpi-pico-w/src/bin/tcp_server.rs b/examples/rpi-pico-w/src/bin/tcp_server.rs index 8accc469..6a87e7c5 100644 --- a/examples/rpi-pico-w/src/bin/tcp_server.rs +++ b/examples/rpi-pico-w/src/bin/tcp_server.rs @@ -28,7 +28,11 @@ macro_rules! singleton { #[embassy_executor::task] async fn wifi_task( - runner: cyw43::Runner<'static, Output<'static, PIN_23>, PioSpi<'static, PIN_25, PIO0, 0, DMA_CH0>>, + runner: cyw43::Runner< + 'static, + Output<'static, PIN_23>, + PioSpi<'static, PIN_25, PIO0, 0, DMA_CH0>, + >, ) -> ! { runner.run().await } @@ -44,8 +48,8 @@ async fn main(spawner: Spawner) { let p = embassy_rp::init(Default::default()); - let fw = include_bytes!("../../../../firmware/43439A0.bin"); - let clm = include_bytes!("../../../../firmware/43439A0_clm.bin"); + let fw = include_bytes!("../../../../cyw43-firmware/43439A0.bin"); + let clm = include_bytes!("../../../../cyw43-firmware/43439A0_clm.bin"); // To make flashing faster for development, you may want to flash the firmwares independently // at hardcoded addresses, instead of baking them into the program with `include_bytes!`: @@ -57,7 +61,15 @@ async fn main(spawner: Spawner) { let pwr = Output::new(p.PIN_23, Level::Low); let cs = Output::new(p.PIN_25, Level::High); let mut pio = Pio::new(p.PIO0); - let spi = PioSpi::new(&mut pio.common, pio.sm0, pio.irq0, cs, p.PIN_24, p.PIN_29, p.DMA_CH0); + let spi = PioSpi::new( + &mut pio.common, + pio.sm0, + pio.irq0, + cs, + p.PIN_24, + p.PIN_29, + p.DMA_CH0, + ); let state = singleton!(cyw43::State::new()); let (net_device, mut control, runner) = cyw43::new(state, pwr, spi, fw).await; @@ -90,7 +102,10 @@ async fn main(spawner: Spawner) { loop { //control.join_open(env!("WIFI_NETWORK")).await; - match control.join_wpa2(env!("WIFI_NETWORK"), env!("WIFI_PASSWORD")).await { + match control + .join_wpa2(env!("WIFI_NETWORK"), env!("WIFI_PASSWORD")) + .await + { Ok(_) => break, Err(err) => { info!("join failed with status={}", err.status); diff --git a/examples/rpi-pico-w/src/bin/tcp_server_ap.rs b/examples/rpi-pico-w/src/bin/tcp_server_ap.rs index ee2c3237..24ff7767 100644 --- a/examples/rpi-pico-w/src/bin/tcp_server_ap.rs +++ b/examples/rpi-pico-w/src/bin/tcp_server_ap.rs @@ -28,7 +28,11 @@ macro_rules! singleton { #[embassy_executor::task] async fn wifi_task( - runner: cyw43::Runner<'static, Output<'static, PIN_23>, PioSpi<'static, PIN_25, PIO0, 0, DMA_CH0>>, + runner: cyw43::Runner< + 'static, + Output<'static, PIN_23>, + PioSpi<'static, PIN_25, PIO0, 0, DMA_CH0>, + >, ) -> ! { runner.run().await } @@ -44,8 +48,8 @@ async fn main(spawner: Spawner) { let p = embassy_rp::init(Default::default()); - let fw = include_bytes!("../../../../firmware/43439A0.bin"); - let clm = include_bytes!("../../../../firmware/43439A0_clm.bin"); + let fw = include_bytes!("../../../../cyw43-firmware/43439A0.bin"); + let clm = include_bytes!("../../../../cyw43-firmware/43439A0_clm.bin"); // To make flashing faster for development, you may want to flash the firmwares independently // at hardcoded addresses, instead of baking them into the program with `include_bytes!`: @@ -57,7 +61,15 @@ async fn main(spawner: Spawner) { let pwr = Output::new(p.PIN_23, Level::Low); let cs = Output::new(p.PIN_25, Level::High); let mut pio = Pio::new(p.PIO0); - let spi = PioSpi::new(&mut pio.common, pio.sm0, pio.irq0, cs, p.PIN_24, p.PIN_29, p.DMA_CH0); + let spi = PioSpi::new( + &mut pio.common, + pio.sm0, + pio.irq0, + cs, + p.PIN_24, + p.PIN_29, + p.DMA_CH0, + ); let state = singleton!(cyw43::State::new()); let (net_device, mut control, runner) = cyw43::new(state, pwr, spi, fw).await; diff --git a/examples/rpi-pico-w/src/bin/wifi_scan.rs b/examples/rpi-pico-w/src/bin/wifi_scan.rs index a2a44f99..8fb6c65a 100644 --- a/examples/rpi-pico-w/src/bin/wifi_scan.rs +++ b/examples/rpi-pico-w/src/bin/wifi_scan.rs @@ -26,7 +26,11 @@ macro_rules! singleton { #[embassy_executor::task] async fn wifi_task( - runner: cyw43::Runner<'static, Output<'static, PIN_23>, PioSpi<'static, PIN_25, PIO0, 0, DMA_CH0>>, + runner: cyw43::Runner< + 'static, + Output<'static, PIN_23>, + PioSpi<'static, PIN_25, PIO0, 0, DMA_CH0>, + >, ) -> ! { runner.run().await } @@ -42,8 +46,8 @@ async fn main(spawner: Spawner) { let p = embassy_rp::init(Default::default()); - let fw = include_bytes!("../../../../firmware/43439A0.bin"); - let clm = include_bytes!("../../../../firmware/43439A0_clm.bin"); + let fw = include_bytes!("../../../../cyw43-firmware/43439A0.bin"); + let clm = include_bytes!("../../../../cyw43-firmware/43439A0_clm.bin"); // To make flashing faster for development, you may want to flash the firmwares independently // at hardcoded addresses, instead of baking them into the program with `include_bytes!`: @@ -55,7 +59,15 @@ async fn main(spawner: Spawner) { let pwr = Output::new(p.PIN_23, Level::Low); let cs = Output::new(p.PIN_25, Level::High); let mut pio = Pio::new(p.PIO0); - let spi = PioSpi::new(&mut pio.common, pio.sm0, pio.irq0, cs, p.PIN_24, p.PIN_29, p.DMA_CH0); + let spi = PioSpi::new( + &mut pio.common, + pio.sm0, + pio.irq0, + cs, + p.PIN_24, + p.PIN_29, + p.DMA_CH0, + ); let state = singleton!(cyw43::State::new()); let (_net_device, mut control, runner) = cyw43::new(state, pwr, spi, fw).await; diff --git a/rust-toolchain.toml b/rust-toolchain.toml deleted file mode 100644 index 2582e88f..00000000 --- a/rust-toolchain.toml +++ /dev/null @@ -1,8 +0,0 @@ -# Before upgrading check that everything is available on all tier1 targets here: -# https://rust-lang.github.io/rustup-components-history -[toolchain] -channel = "nightly-2023-04-18" -components = [ "rust-src", "rustfmt" ] -targets = [ - "thumbv6m-none-eabi", -] diff --git a/rustfmt.toml b/rustfmt.toml deleted file mode 100644 index 3639f438..00000000 --- a/rustfmt.toml +++ /dev/null @@ -1,3 +0,0 @@ -group_imports = "StdExternalCrate" -imports_granularity = "Module" -max_width=120 \ No newline at end of file From 3f35a8876ee65d030e32ab2444bc0abb29d88382 Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Tue, 30 May 2023 23:22:34 +0200 Subject: [PATCH 175/178] cyw43: adapt build to main embassy repo. --- ci.sh | 11 + cyw43-pio/Cargo.toml | 6 +- cyw43/Cargo.toml | 20 +- examples/rp/Cargo.toml | 3 + .../src/bin/wifi_ap_tcp_server.rs} | 19 +- .../{rpi-pico-w => rp}/src/bin/wifi_scan.rs | 16 +- .../src/bin/wifi_tcp_server.rs} | 24 +- examples/rpi-pico-w/.cargo/config.toml | 8 - examples/rpi-pico-w/Cargo.lock | 1707 ----------------- examples/rpi-pico-w/Cargo.toml | 67 - examples/rpi-pico-w/build.rs | 36 - examples/rpi-pico-w/memory.x | 5 - 12 files changed, 32 insertions(+), 1890 deletions(-) rename examples/{rpi-pico-w/src/bin/tcp_server_ap.rs => rp/src/bin/wifi_ap_tcp_server.rs} (91%) rename examples/{rpi-pico-w => rp}/src/bin/wifi_scan.rs (88%) rename examples/{rpi-pico-w/src/bin/tcp_server.rs => rp/src/bin/wifi_tcp_server.rs} (89%) delete mode 100644 examples/rpi-pico-w/.cargo/config.toml delete mode 100644 examples/rpi-pico-w/Cargo.lock delete mode 100644 examples/rpi-pico-w/Cargo.toml delete mode 100644 examples/rpi-pico-w/build.rs delete mode 100644 examples/rpi-pico-w/memory.x diff --git a/ci.sh b/ci.sh index 1eafda3a..8a3669f0 100755 --- a/ci.sh +++ b/ci.sh @@ -5,6 +5,10 @@ set -euo pipefail export RUSTFLAGS=-Dwarnings export DEFMT_LOG=trace +# needed by wifi examples +export WIFI_NETWORK=x +export WIFI_PASSWORD=x + TARGET=$(rustc -vV | sed -n 's|host: ||p') BUILD_EXTRA="" @@ -82,6 +86,13 @@ cargo batch \ --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7m-none-eabi --features nightly,stm32f100c4,defmt,exti,time-driver-any,unstable-traits \ --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7m-none-eabi --features nightly,stm32h503rb,defmt,exti,time-driver-any,unstable-traits \ --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7m-none-eabi --features nightly,stm32h562ag,defmt,exti,time-driver-any,unstable-traits \ + --- build --release --manifest-path cyw43/Cargo.toml --target thumbv6m-none-eabi --features ''\ + --- build --release --manifest-path cyw43/Cargo.toml --target thumbv6m-none-eabi --features 'log' \ + --- build --release --manifest-path cyw43/Cargo.toml --target thumbv6m-none-eabi --features 'defmt' \ + --- build --release --manifest-path cyw43/Cargo.toml --target thumbv6m-none-eabi --features 'log,firmware-logs' \ + --- build --release --manifest-path cyw43/Cargo.toml --target thumbv6m-none-eabi --features 'defmt,firmware-logs' \ + --- build --release --manifest-path cyw43-pio/Cargo.toml --target thumbv6m-none-eabi --features '' \ + --- build --release --manifest-path cyw43-pio/Cargo.toml --target thumbv6m-none-eabi --features 'overclock' \ --- build --release --manifest-path embassy-boot/nrf/Cargo.toml --target thumbv7em-none-eabi --features embassy-nrf/nrf52840,nightly \ --- build --release --manifest-path embassy-boot/nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features embassy-nrf/nrf9160-ns,nightly \ --- build --release --manifest-path embassy-boot/rp/Cargo.toml --target thumbv6m-none-eabi --features nightly \ diff --git a/cyw43-pio/Cargo.toml b/cyw43-pio/Cargo.toml index a7af2c8e..6e9e784a 100644 --- a/cyw43-pio/Cargo.toml +++ b/cyw43-pio/Cargo.toml @@ -9,9 +9,9 @@ edition = "2021" overclock = [] [dependencies] -cyw43 = { path = "../cyw43" } -embassy-rp = { version = "0.1.0", features = ["unstable-traits", "nightly", "unstable-pac", "time-driver"] } +cyw43 = { version = "0.1.0", path = "../cyw43" } +embassy-rp = { version = "0.1.0", path = "../embassy-rp" } pio-proc = "0.2" pio = "0.2.1" fixed = "1.23.1" -defmt = { version = "0.3", optional = true } \ No newline at end of file +defmt = { version = "0.3", optional = true } diff --git a/cyw43/Cargo.toml b/cyw43/Cargo.toml index 2bb2b8d9..c7f8816f 100644 --- a/cyw43/Cargo.toml +++ b/cyw43/Cargo.toml @@ -11,10 +11,10 @@ log = ["dep:log"] firmware-logs = [] [dependencies] -embassy-time = { version = "0.1.0" } -embassy-sync = { version = "0.2.0" } -embassy-futures = { version = "0.1.0" } -embassy-net-driver-channel = { version = "0.1.0" } +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"} atomic-polyfill = "0.1.5" defmt = { version = "0.3", optional = true } @@ -26,15 +26,3 @@ futures = { version = "0.3.17", default-features = false, features = ["async-awa embedded-hal-1 = { package = "embedded-hal", version = "1.0.0-alpha.10" } num_enum = { version = "0.5.7", default-features = false } - -[patch.crates-io] -embassy-time = { git = "https://github.com/embassy-rs/embassy", rev = "82f7e104d90a6628d1873017ea5ef6a7afb3b3f7" } -embassy-futures = { git = "https://github.com/embassy-rs/embassy", rev = "82f7e104d90a6628d1873017ea5ef6a7afb3b3f7" } -embassy-sync = { git = "https://github.com/embassy-rs/embassy", rev = "82f7e104d90a6628d1873017ea5ef6a7afb3b3f7" } -embassy-net-driver-channel = { git = "https://github.com/embassy-rs/embassy", rev = "82f7e104d90a6628d1873017ea5ef6a7afb3b3f7" } -embassy-rp = { git = "https://github.com/embassy-rs/embassy", rev = "82f7e104d90a6628d1873017ea5ef6a7afb3b3f7" } - -[workspace] -members = ["cyw43-pio"] -default-members = ["cyw43-pio", "."] -exclude = ["examples"] \ No newline at end of file diff --git a/examples/rp/Cargo.toml b/examples/rp/Cargo.toml index ffeb69f1..f77377a6 100644 --- a/examples/rp/Cargo.toml +++ b/examples/rp/Cargo.toml @@ -19,6 +19,8 @@ embassy-lora = { version = "0.1.0", path = "../../embassy-lora", features = ["ti lora-phy = { version = "1" } lorawan-device = { version = "0.10.0", default-features = false, features = ["async", "external-lora-phy"] } lorawan = { version = "0.7.3", default-features = false, features = ["default-crypto"] } +cyw43 = { path = "../../cyw43", features = ["defmt", "firmware-logs"] } +cyw43-pio = { path = "../../cyw43-pio", features = ["defmt", "overclock"] } defmt = "0.3" defmt-rtt = "0.4" @@ -36,6 +38,7 @@ st7789 = "0.6.1" display-interface = "0.4.1" byte-slice-cast = { version = "1.2.0", default-features = false } smart-leds = "0.3.0" +heapless = "0.7.15" embedded-hal-1 = { package = "embedded-hal", version = "=1.0.0-alpha.10" } embedded-hal-async = "0.2.0-alpha.1" diff --git a/examples/rpi-pico-w/src/bin/tcp_server_ap.rs b/examples/rp/src/bin/wifi_ap_tcp_server.rs similarity index 91% rename from examples/rpi-pico-w/src/bin/tcp_server_ap.rs rename to examples/rp/src/bin/wifi_ap_tcp_server.rs index 24ff7767..15264524 100644 --- a/examples/rpi-pico-w/src/bin/tcp_server_ap.rs +++ b/examples/rp/src/bin/wifi_ap_tcp_server.rs @@ -14,6 +14,7 @@ use embassy_net::{Config, Stack, StackResources}; use embassy_rp::gpio::{Level, Output}; use embassy_rp::peripherals::{DMA_CH0, PIN_23, PIN_25, PIO0}; use embassy_rp::pio::Pio; +use embassy_time::Duration; use embedded_io::asynch::Write; use static_cell::StaticCell; use {defmt_rtt as _, panic_probe as _}; @@ -28,11 +29,7 @@ macro_rules! singleton { #[embassy_executor::task] async fn wifi_task( - runner: cyw43::Runner< - 'static, - Output<'static, PIN_23>, - PioSpi<'static, PIN_25, PIO0, 0, DMA_CH0>, - >, + runner: cyw43::Runner<'static, Output<'static, PIN_23>, PioSpi<'static, PIN_25, PIO0, 0, DMA_CH0>>, ) -> ! { runner.run().await } @@ -61,15 +58,7 @@ async fn main(spawner: Spawner) { let pwr = Output::new(p.PIN_23, Level::Low); let cs = Output::new(p.PIN_25, Level::High); let mut pio = Pio::new(p.PIO0); - let spi = PioSpi::new( - &mut pio.common, - pio.sm0, - pio.irq0, - cs, - p.PIN_24, - p.PIN_29, - p.DMA_CH0, - ); + let spi = PioSpi::new(&mut pio.common, pio.sm0, pio.irq0, cs, p.PIN_24, p.PIN_29, p.DMA_CH0); let state = singleton!(cyw43::State::new()); let (net_device, mut control, runner) = cyw43::new(state, pwr, spi, fw).await; @@ -111,7 +100,7 @@ async fn main(spawner: Spawner) { loop { let mut socket = TcpSocket::new(stack, &mut rx_buffer, &mut tx_buffer); - socket.set_timeout(Some(embassy_net::SmolDuration::from_secs(10))); + socket.set_timeout(Some(Duration::from_secs(10))); control.gpio_set(0, false).await; info!("Listening on TCP:1234..."); diff --git a/examples/rpi-pico-w/src/bin/wifi_scan.rs b/examples/rp/src/bin/wifi_scan.rs similarity index 88% rename from examples/rpi-pico-w/src/bin/wifi_scan.rs rename to examples/rp/src/bin/wifi_scan.rs index 8fb6c65a..aa5e5a39 100644 --- a/examples/rpi-pico-w/src/bin/wifi_scan.rs +++ b/examples/rp/src/bin/wifi_scan.rs @@ -26,11 +26,7 @@ macro_rules! singleton { #[embassy_executor::task] async fn wifi_task( - runner: cyw43::Runner< - 'static, - Output<'static, PIN_23>, - PioSpi<'static, PIN_25, PIO0, 0, DMA_CH0>, - >, + runner: cyw43::Runner<'static, Output<'static, PIN_23>, PioSpi<'static, PIN_25, PIO0, 0, DMA_CH0>>, ) -> ! { runner.run().await } @@ -59,15 +55,7 @@ async fn main(spawner: Spawner) { let pwr = Output::new(p.PIN_23, Level::Low); let cs = Output::new(p.PIN_25, Level::High); let mut pio = Pio::new(p.PIO0); - let spi = PioSpi::new( - &mut pio.common, - pio.sm0, - pio.irq0, - cs, - p.PIN_24, - p.PIN_29, - p.DMA_CH0, - ); + let spi = PioSpi::new(&mut pio.common, pio.sm0, pio.irq0, cs, p.PIN_24, p.PIN_29, p.DMA_CH0); let state = singleton!(cyw43::State::new()); let (_net_device, mut control, runner) = cyw43::new(state, pwr, spi, fw).await; diff --git a/examples/rpi-pico-w/src/bin/tcp_server.rs b/examples/rp/src/bin/wifi_tcp_server.rs similarity index 89% rename from examples/rpi-pico-w/src/bin/tcp_server.rs rename to examples/rp/src/bin/wifi_tcp_server.rs index 6a87e7c5..eafa25f6 100644 --- a/examples/rpi-pico-w/src/bin/tcp_server.rs +++ b/examples/rp/src/bin/wifi_tcp_server.rs @@ -14,6 +14,7 @@ use embassy_net::{Config, Stack, StackResources}; use embassy_rp::gpio::{Level, Output}; use embassy_rp::peripherals::{DMA_CH0, PIN_23, PIN_25, PIO0}; use embassy_rp::pio::Pio; +use embassy_time::Duration; use embedded_io::asynch::Write; use static_cell::StaticCell; use {defmt_rtt as _, panic_probe as _}; @@ -28,11 +29,7 @@ macro_rules! singleton { #[embassy_executor::task] async fn wifi_task( - runner: cyw43::Runner< - 'static, - Output<'static, PIN_23>, - PioSpi<'static, PIN_25, PIO0, 0, DMA_CH0>, - >, + runner: cyw43::Runner<'static, Output<'static, PIN_23>, PioSpi<'static, PIN_25, PIO0, 0, DMA_CH0>>, ) -> ! { runner.run().await } @@ -61,15 +58,7 @@ async fn main(spawner: Spawner) { let pwr = Output::new(p.PIN_23, Level::Low); let cs = Output::new(p.PIN_25, Level::High); let mut pio = Pio::new(p.PIO0); - let spi = PioSpi::new( - &mut pio.common, - pio.sm0, - pio.irq0, - cs, - p.PIN_24, - p.PIN_29, - p.DMA_CH0, - ); + let spi = PioSpi::new(&mut pio.common, pio.sm0, pio.irq0, cs, p.PIN_24, p.PIN_29, p.DMA_CH0); let state = singleton!(cyw43::State::new()); let (net_device, mut control, runner) = cyw43::new(state, pwr, spi, fw).await; @@ -102,10 +91,7 @@ async fn main(spawner: Spawner) { loop { //control.join_open(env!("WIFI_NETWORK")).await; - match control - .join_wpa2(env!("WIFI_NETWORK"), env!("WIFI_PASSWORD")) - .await - { + match control.join_wpa2(env!("WIFI_NETWORK"), env!("WIFI_PASSWORD")).await { Ok(_) => break, Err(err) => { info!("join failed with status={}", err.status); @@ -121,7 +107,7 @@ async fn main(spawner: Spawner) { loop { let mut socket = TcpSocket::new(stack, &mut rx_buffer, &mut tx_buffer); - socket.set_timeout(Some(embassy_net::SmolDuration::from_secs(10))); + socket.set_timeout(Some(Duration::from_secs(10))); control.gpio_set(0, false).await; info!("Listening on TCP:1234..."); diff --git a/examples/rpi-pico-w/.cargo/config.toml b/examples/rpi-pico-w/.cargo/config.toml deleted file mode 100644 index f1ed8af9..00000000 --- a/examples/rpi-pico-w/.cargo/config.toml +++ /dev/null @@ -1,8 +0,0 @@ -[target.'cfg(all(target_arch = "arm", target_os = "none"))'] -runner = "probe-rs-cli run --chip RP2040" - -[build] -target = "thumbv6m-none-eabi" - -[env] -DEFMT_LOG = "debug" diff --git a/examples/rpi-pico-w/Cargo.lock b/examples/rpi-pico-w/Cargo.lock deleted file mode 100644 index 16095835..00000000 --- a/examples/rpi-pico-w/Cargo.lock +++ /dev/null @@ -1,1707 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 3 - -[[package]] -name = "aho-corasick" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67fc08ce920c31afb70f013dcce1bfc3a3195de6a228474e45e1f145b36f8d04" -dependencies = [ - "memchr", -] - -[[package]] -name = "arrayvec" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6" - -[[package]] -name = "as-slice" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45403b49e3954a4b8428a0ac21a4b7afadccf92bfd96273f1a58cd4812496ae0" -dependencies = [ - "generic-array 0.12.4", - "generic-array 0.13.3", - "generic-array 0.14.7", - "stable_deref_trait", -] - -[[package]] -name = "as-slice" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "516b6b4f0e40d50dcda9365d53964ec74560ad4284da2e7fc97122cd83174516" -dependencies = [ - "stable_deref_trait", -] - -[[package]] -name = "ascii-canvas" -version = "3.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8824ecca2e851cec16968d54a01dd372ef8f95b244fb84b84e70128be347c3c6" -dependencies = [ - "term", -] - -[[package]] -name = "atomic-polyfill" -version = "0.1.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3ff7eb3f316534d83a8a2c3d1674ace8a5a71198eba31e2e2b597833f699b28" -dependencies = [ - "critical-section 1.1.1", -] - -[[package]] -name = "atomic-polyfill" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c314e70d181aa6053b26e3f7fbf86d1dfff84f816a6175b967666b3506ef7289" -dependencies = [ - "critical-section 1.1.1", -] - -[[package]] -name = "atomic-pool" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58c5fc22e05ec2884db458bf307dc7b278c9428888d2b6e6fad9c0ae7804f5f6" -dependencies = [ - "as-slice 0.1.5", - "as-slice 0.2.1", - "atomic-polyfill 1.0.2", - "stable_deref_trait", -] - -[[package]] -name = "autocfg" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" - -[[package]] -name = "az" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b7e4c2464d97fe331d41de9d5db0def0a96f4d823b8b32a2efd503578988973" - -[[package]] -name = "bare-metal" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5deb64efa5bd81e31fcd1938615a6d98c82eafcbcd787162b6f63b91d6bac5b3" -dependencies = [ - "rustc_version 0.2.3", -] - -[[package]] -name = "bare-metal" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8fe8f5a8a398345e52358e18ff07cc17a568fbca5c6f73873d3a62056309603" - -[[package]] -name = "bit-set" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" -dependencies = [ - "bit-vec", -] - -[[package]] -name = "bit-vec" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" - -[[package]] -name = "bitfield" -version = "0.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46afbd2983a5d5a7bd740ccb198caf5b82f45c40c09c0eed36052d91cb92e719" - -[[package]] -name = "bitflags" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - -[[package]] -name = "bytemuck" -version = "1.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17febce684fd15d89027105661fec94afb475cb995fbc59d2865198446ba2eea" - -[[package]] -name = "byteorder" -version = "1.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" - -[[package]] -name = "cc" -version = "1.0.79" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" - -[[package]] -name = "cfg-if" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" - -[[package]] -name = "codespan-reporting" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" -dependencies = [ - "termcolor", - "unicode-width", -] - -[[package]] -name = "cortex-m" -version = "0.7.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ec610d8f49840a5b376c69663b6369e71f4b34484b9b2eb29fb918d92516cb9" -dependencies = [ - "bare-metal 0.2.5", - "bitfield", - "critical-section 1.1.1", - "embedded-hal 0.2.7", - "volatile-register", -] - -[[package]] -name = "cortex-m-rt" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee84e813d593101b1723e13ec38b6ab6abbdbaaa4546553f5395ed274079ddb1" -dependencies = [ - "cortex-m-rt-macros", -] - -[[package]] -name = "cortex-m-rt-macros" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0f6f3e36f203cfedbc78b357fb28730aa2c6dc1ab060ee5c2405e843988d3c7" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "crc-any" -version = "2.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "774646b687f63643eb0f4bf13dc263cb581c8c9e57973b6ddf78bda3994d88df" -dependencies = [ - "debug-helper", -] - -[[package]] -name = "critical-section" -version = "0.2.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1706d332edc22aef4d9f23a6bb1c92360a403013c291af51247a737472dcae6" -dependencies = [ - "bare-metal 1.0.0", - "critical-section 1.1.1", -] - -[[package]] -name = "critical-section" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6548a0ad5d2549e111e1f6a11a6c2e2d00ce6a3dafe22948d67c2b443f775e52" - -[[package]] -name = "crunchy" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" - -[[package]] -name = "cyw43" -version = "0.1.0" -dependencies = [ - "atomic-polyfill 0.1.11", - "cortex-m", - "cortex-m-rt", - "defmt", - "embassy-futures", - "embassy-net-driver-channel", - "embassy-sync", - "embassy-time", - "embedded-hal 1.0.0-alpha.10", - "futures", - "num_enum", -] - -[[package]] -name = "cyw43-example-rpi-pico-w" -version = "0.1.0" -dependencies = [ - "atomic-polyfill 0.1.11", - "cortex-m", - "cortex-m-rt", - "cyw43", - "cyw43-pio", - "defmt", - "defmt-rtt", - "embassy-executor", - "embassy-net", - "embassy-rp", - "embassy-time", - "embedded-io", - "futures", - "heapless", - "panic-probe", - "static_cell", -] - -[[package]] -name = "cyw43-pio" -version = "0.1.0" -dependencies = [ - "cyw43", - "defmt", - "embassy-rp", - "fixed", - "pio", - "pio-proc", -] - -[[package]] -name = "darling" -version = "0.13.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a01d95850c592940db9b8194bc39f4bc0e89dee5c4265e4b1807c34a9aba453c" -dependencies = [ - "darling_core", - "darling_macro", -] - -[[package]] -name = "darling_core" -version = "0.13.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "859d65a907b6852c9361e3185c862aae7fafd2887876799fa55f5f99dc40d610" -dependencies = [ - "fnv", - "ident_case", - "proc-macro2", - "quote", - "strsim", - "syn 1.0.109", -] - -[[package]] -name = "darling_macro" -version = "0.13.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c972679f83bdf9c42bd905396b6c3588a843a17f0f16dfcfa3e2c5d57441835" -dependencies = [ - "darling_core", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "debug-helper" -version = "0.3.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f578e8e2c440e7297e008bb5486a3a8a194775224bbc23729b0dbdfaeebf162e" - -[[package]] -name = "defmt" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "956673bd3cb347512bf988d1e8d89ac9a82b64f6eec54d3c01c3529dac019882" -dependencies = [ - "bitflags", - "defmt-macros", -] - -[[package]] -name = "defmt-macros" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4abc4821bd84d3d8f49945ddb24d029be9385ed9b77c99bf2f6296847a6a9f0" -dependencies = [ - "defmt-parser", - "proc-macro-error", - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "defmt-parser" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "269924c02afd7f94bc4cecbfa5c379f6ffcf9766b3408fe63d22c728654eccd0" -dependencies = [ - "thiserror", -] - -[[package]] -name = "defmt-rtt" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d2cbbbd58847d508d97629b32cd9730a2d28532f71e219714614406029f18b1" -dependencies = [ - "critical-section 0.2.8", - "defmt", -] - -[[package]] -name = "diff" -version = "0.1.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" - -[[package]] -name = "dirs-next" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" -dependencies = [ - "cfg-if", - "dirs-sys-next", -] - -[[package]] -name = "dirs-sys-next" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" -dependencies = [ - "libc", - "redox_users", - "winapi", -] - -[[package]] -name = "either" -version = "1.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" - -[[package]] -name = "embassy-cortex-m" -version = "0.1.0" -source = "git+https://github.com/embassy-rs/embassy?rev=82f7e104d90a6628d1873017ea5ef6a7afb3b3f7#82f7e104d90a6628d1873017ea5ef6a7afb3b3f7" -dependencies = [ - "atomic-polyfill 1.0.2", - "cfg-if", - "cortex-m", - "critical-section 1.1.1", - "embassy-executor", - "embassy-hal-common", - "embassy-macros", - "embassy-sync", -] - -[[package]] -name = "embassy-embedded-hal" -version = "0.1.0" -source = "git+https://github.com/embassy-rs/embassy?rev=82f7e104d90a6628d1873017ea5ef6a7afb3b3f7#82f7e104d90a6628d1873017ea5ef6a7afb3b3f7" -dependencies = [ - "embassy-sync", - "embedded-hal 0.2.7", - "embedded-hal 1.0.0-alpha.10", - "embedded-hal-async", - "embedded-storage", - "embedded-storage-async", - "nb 1.1.0", -] - -[[package]] -name = "embassy-executor" -version = "0.2.0" -source = "git+https://github.com/embassy-rs/embassy?rev=82f7e104d90a6628d1873017ea5ef6a7afb3b3f7#82f7e104d90a6628d1873017ea5ef6a7afb3b3f7" -dependencies = [ - "atomic-polyfill 1.0.2", - "cortex-m", - "critical-section 1.1.1", - "defmt", - "embassy-macros", - "embassy-time", - "futures-util", - "static_cell", -] - -[[package]] -name = "embassy-futures" -version = "0.1.0" -source = "git+https://github.com/embassy-rs/embassy?rev=82f7e104d90a6628d1873017ea5ef6a7afb3b3f7#82f7e104d90a6628d1873017ea5ef6a7afb3b3f7" - -[[package]] -name = "embassy-hal-common" -version = "0.1.0" -source = "git+https://github.com/embassy-rs/embassy?rev=82f7e104d90a6628d1873017ea5ef6a7afb3b3f7#82f7e104d90a6628d1873017ea5ef6a7afb3b3f7" -dependencies = [ - "defmt", - "num-traits", -] - -[[package]] -name = "embassy-macros" -version = "0.2.0" -source = "git+https://github.com/embassy-rs/embassy?rev=82f7e104d90a6628d1873017ea5ef6a7afb3b3f7#82f7e104d90a6628d1873017ea5ef6a7afb3b3f7" -dependencies = [ - "darling", - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "embassy-net" -version = "0.1.0" -source = "git+https://github.com/embassy-rs/embassy?rev=82f7e104d90a6628d1873017ea5ef6a7afb3b3f7#82f7e104d90a6628d1873017ea5ef6a7afb3b3f7" -dependencies = [ - "as-slice 0.2.1", - "atomic-polyfill 1.0.2", - "atomic-pool", - "defmt", - "embassy-hal-common", - "embassy-net-driver", - "embassy-sync", - "embassy-time", - "embedded-io", - "embedded-nal-async", - "futures", - "generic-array 0.14.7", - "heapless", - "managed", - "smoltcp", - "stable_deref_trait", -] - -[[package]] -name = "embassy-net-driver" -version = "0.1.0" -source = "git+https://github.com/embassy-rs/embassy?rev=82f7e104d90a6628d1873017ea5ef6a7afb3b3f7#82f7e104d90a6628d1873017ea5ef6a7afb3b3f7" -dependencies = [ - "defmt", -] - -[[package]] -name = "embassy-net-driver-channel" -version = "0.1.0" -source = "git+https://github.com/embassy-rs/embassy?rev=82f7e104d90a6628d1873017ea5ef6a7afb3b3f7#82f7e104d90a6628d1873017ea5ef6a7afb3b3f7" -dependencies = [ - "embassy-futures", - "embassy-net-driver", - "embassy-sync", -] - -[[package]] -name = "embassy-rp" -version = "0.1.0" -source = "git+https://github.com/embassy-rs/embassy?rev=82f7e104d90a6628d1873017ea5ef6a7afb3b3f7#82f7e104d90a6628d1873017ea5ef6a7afb3b3f7" -dependencies = [ - "atomic-polyfill 1.0.2", - "cfg-if", - "cortex-m", - "cortex-m-rt", - "critical-section 1.1.1", - "defmt", - "embassy-cortex-m", - "embassy-embedded-hal", - "embassy-executor", - "embassy-futures", - "embassy-hal-common", - "embassy-sync", - "embassy-time", - "embassy-usb-driver", - "embedded-hal 0.2.7", - "embedded-hal 1.0.0-alpha.10", - "embedded-hal-async", - "embedded-hal-nb", - "embedded-io", - "embedded-storage", - "fixed", - "futures", - "nb 1.1.0", - "paste", - "pio", - "pio-proc", - "rand_core", - "rp-pac", - "rp2040-boot2", -] - -[[package]] -name = "embassy-sync" -version = "0.2.0" -source = "git+https://github.com/embassy-rs/embassy?rev=82f7e104d90a6628d1873017ea5ef6a7afb3b3f7#82f7e104d90a6628d1873017ea5ef6a7afb3b3f7" -dependencies = [ - "cfg-if", - "critical-section 1.1.1", - "embedded-io", - "futures-util", - "heapless", -] - -[[package]] -name = "embassy-time" -version = "0.1.1" -source = "git+https://github.com/embassy-rs/embassy?rev=82f7e104d90a6628d1873017ea5ef6a7afb3b3f7#82f7e104d90a6628d1873017ea5ef6a7afb3b3f7" -dependencies = [ - "atomic-polyfill 1.0.2", - "cfg-if", - "critical-section 1.1.1", - "defmt", - "embedded-hal 0.2.7", - "futures-util", - "heapless", -] - -[[package]] -name = "embassy-usb-driver" -version = "0.1.0" -source = "git+https://github.com/embassy-rs/embassy?rev=82f7e104d90a6628d1873017ea5ef6a7afb3b3f7#82f7e104d90a6628d1873017ea5ef6a7afb3b3f7" -dependencies = [ - "defmt", -] - -[[package]] -name = "embedded-hal" -version = "0.2.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35949884794ad573cf46071e41c9b60efb0cb311e3ca01f7af807af1debc66ff" -dependencies = [ - "nb 0.1.3", - "void", -] - -[[package]] -name = "embedded-hal" -version = "1.0.0-alpha.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f65c4d073f5d91c66e629b216818a4c9747eeda0debedf2deda9a0a947e4e93b" - -[[package]] -name = "embedded-hal-async" -version = "0.2.0-alpha.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8042370aa7af48de36d5312cda14c18ed8ca6b7ce64f5a07832fedc9dc83063f" -dependencies = [ - "embedded-hal 1.0.0-alpha.10", -] - -[[package]] -name = "embedded-hal-nb" -version = "1.0.0-alpha.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1465fffd56a95bbc105c17965bca1c1d5815027b1cc6bb183bc05d04563d065c" -dependencies = [ - "embedded-hal 1.0.0-alpha.10", - "nb 1.1.0", -] - -[[package]] -name = "embedded-io" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef1a6892d9eef45c8fa6b9e0086428a2cca8491aca8f787c534a3d6d0bcb3ced" -dependencies = [ - "defmt", -] - -[[package]] -name = "embedded-nal" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db9efecb57ab54fa918730f2874d7d37647169c50fa1357fecb81abee840b113" -dependencies = [ - "heapless", - "nb 1.1.0", - "no-std-net 0.5.0", -] - -[[package]] -name = "embedded-nal-async" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27ce84f518ca912777ec143db235f4d615e3bf8d4e46d507d6ef12daf5b1df98" -dependencies = [ - "embedded-io", - "embedded-nal", - "heapless", - "no-std-net 0.6.0", -] - -[[package]] -name = "embedded-storage" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "156d7a2fdd98ebbf9ae579cbceca3058cff946e13f8e17b90e3511db0508c723" - -[[package]] -name = "embedded-storage-async" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "052997a894670d0cde873faa7405bc98e2fd29f569d2acd568561bc1c396b35a" -dependencies = [ - "embedded-storage", -] - -[[package]] -name = "ena" -version = "0.14.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c533630cf40e9caa44bd91aadc88a75d75a4c3a12b4cfde353cbed41daa1e1f1" -dependencies = [ - "log", -] - -[[package]] -name = "errno" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" -dependencies = [ - "errno-dragonfly", - "libc", - "windows-sys 0.48.0", -] - -[[package]] -name = "errno-dragonfly" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" -dependencies = [ - "cc", - "libc", -] - -[[package]] -name = "fixed" -version = "1.23.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79386fdcec5e0fde91b1a6a5bcd89677d1f9304f7f986b154a1b9109038854d9" -dependencies = [ - "az", - "bytemuck", - "half", - "typenum", -] - -[[package]] -name = "fixedbitset" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" - -[[package]] -name = "fnv" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" - -[[package]] -name = "futures" -version = "0.3.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40" -dependencies = [ - "futures-channel", - "futures-core", - "futures-io", - "futures-sink", - "futures-task", - "futures-util", -] - -[[package]] -name = "futures-channel" -version = "0.3.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" -dependencies = [ - "futures-core", - "futures-sink", -] - -[[package]] -name = "futures-core" -version = "0.3.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" - -[[package]] -name = "futures-io" -version = "0.3.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" - -[[package]] -name = "futures-macro" -version = "0.3.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.18", -] - -[[package]] -name = "futures-sink" -version = "0.3.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" - -[[package]] -name = "futures-task" -version = "0.3.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" - -[[package]] -name = "futures-util" -version = "0.3.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" -dependencies = [ - "futures-core", - "futures-macro", - "futures-sink", - "futures-task", - "pin-project-lite", - "pin-utils", -] - -[[package]] -name = "generic-array" -version = "0.12.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffdf9f34f1447443d37393cc6c2b8313aebddcd96906caf34e54c68d8e57d7bd" -dependencies = [ - "typenum", -] - -[[package]] -name = "generic-array" -version = "0.13.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f797e67af32588215eaaab8327027ee8e71b9dd0b2b26996aedf20c030fce309" -dependencies = [ - "typenum", -] - -[[package]] -name = "generic-array" -version = "0.14.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" -dependencies = [ - "typenum", - "version_check", -] - -[[package]] -name = "getrandom" -version = "0.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c85e1d9ab2eadba7e5040d4e09cbd6d072b76a557ad64e797c2cb9d4da21d7e4" -dependencies = [ - "cfg-if", - "libc", - "wasi", -] - -[[package]] -name = "half" -version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02b4af3693f1b705df946e9fe5631932443781d0aabb423b62fcd4d73f6d2fd0" -dependencies = [ - "crunchy", -] - -[[package]] -name = "hash32" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0c35f58762feb77d74ebe43bdbc3210f09be9fe6742234d573bacc26ed92b67" -dependencies = [ - "byteorder", -] - -[[package]] -name = "hashbrown" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" - -[[package]] -name = "heapless" -version = "0.7.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db04bc24a18b9ea980628ecf00e6c0264f3c1426dac36c00cb49b6fbad8b0743" -dependencies = [ - "atomic-polyfill 0.1.11", - "defmt", - "hash32", - "rustc_version 0.4.0", - "spin", - "stable_deref_trait", -] - -[[package]] -name = "hermit-abi" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" - -[[package]] -name = "ident_case" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" - -[[package]] -name = "indexmap" -version = "1.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" -dependencies = [ - "autocfg", - "hashbrown", -] - -[[package]] -name = "io-lifetimes" -version = "1.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" -dependencies = [ - "hermit-abi", - "libc", - "windows-sys 0.48.0", -] - -[[package]] -name = "is-terminal" -version = "0.4.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adcf93614601c8129ddf72e2d5633df827ba6551541c6d8c59520a371475be1f" -dependencies = [ - "hermit-abi", - "io-lifetimes", - "rustix", - "windows-sys 0.48.0", -] - -[[package]] -name = "itertools" -version = "0.10.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" -dependencies = [ - "either", -] - -[[package]] -name = "lalrpop" -version = "0.19.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a1cbf952127589f2851ab2046af368fd20645491bb4b376f04b7f94d7a9837b" -dependencies = [ - "ascii-canvas", - "bit-set", - "diff", - "ena", - "is-terminal", - "itertools", - "lalrpop-util", - "petgraph", - "regex", - "regex-syntax 0.6.29", - "string_cache", - "term", - "tiny-keccak", - "unicode-xid", -] - -[[package]] -name = "lalrpop-util" -version = "0.19.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3c48237b9604c5a4702de6b824e02006c3214327564636aef27c1028a8fa0ed" -dependencies = [ - "regex", -] - -[[package]] -name = "libc" -version = "0.2.144" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b00cc1c228a6782d0f076e7b232802e0c5689d41bb5df366f2a6b6621cfdfe1" - -[[package]] -name = "linux-raw-sys" -version = "0.3.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" - -[[package]] -name = "lock_api" -version = "0.4.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" -dependencies = [ - "autocfg", - "scopeguard", -] - -[[package]] -name = "log" -version = "0.4.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "518ef76f2f87365916b142844c16d8fefd85039bc5699050210a7778ee1cd1de" - -[[package]] -name = "managed" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ca88d725a0a943b096803bd34e73a4437208b6077654cc4ecb2947a5f91618d" - -[[package]] -name = "memchr" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" - -[[package]] -name = "nb" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "801d31da0513b6ec5214e9bf433a77966320625a37860f910be265be6e18d06f" -dependencies = [ - "nb 1.1.0", -] - -[[package]] -name = "nb" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d5439c4ad607c3c23abf66de8c8bf57ba8adcd1f129e699851a6e43935d339d" - -[[package]] -name = "new_debug_unreachable" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54" - -[[package]] -name = "no-std-net" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bcece43b12349917e096cddfa66107277f123e6c96a5aea78711dc601a47152" - -[[package]] -name = "no-std-net" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43794a0ace135be66a25d3ae77d41b91615fb68ae937f904090203e81f755b65" - -[[package]] -name = "num-traits" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" -dependencies = [ - "autocfg", -] - -[[package]] -name = "num_enum" -version = "0.5.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f646caf906c20226733ed5b1374287eb97e3c2a5c227ce668c1f2ce20ae57c9" -dependencies = [ - "num_enum_derive", -] - -[[package]] -name = "num_enum_derive" -version = "0.5.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcbff9bc912032c62bf65ef1d5aea88983b420f4f839db1e9b0c281a25c9c799" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "once_cell" -version = "1.17.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9670a07f94779e00908f3e686eab508878ebb390ba6e604d3a284c00e8d0487b" - -[[package]] -name = "panic-probe" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa6fa5645ef5a760cd340eaa92af9c1ce131c8c09e7f8926d8a24b59d26652b9" -dependencies = [ - "cortex-m", - "defmt", -] - -[[package]] -name = "parking_lot" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" -dependencies = [ - "lock_api", - "parking_lot_core", -] - -[[package]] -name = "parking_lot_core" -version = "0.9.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9069cbb9f99e3a5083476ccb29ceb1de18b9118cafa53e90c9551235de2b9521" -dependencies = [ - "cfg-if", - "libc", - "redox_syscall", - "smallvec", - "windows-sys 0.45.0", -] - -[[package]] -name = "paste" -version = "1.0.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f746c4065a8fa3fe23974dd82f15431cc8d40779821001404d10d2e79ca7d79" - -[[package]] -name = "petgraph" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4dd7d28ee937e54fe3080c91faa1c3a46c06de6252988a7f4592ba2310ef22a4" -dependencies = [ - "fixedbitset", - "indexmap", -] - -[[package]] -name = "phf_shared" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096" -dependencies = [ - "siphasher", -] - -[[package]] -name = "pin-project-lite" -version = "0.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" - -[[package]] -name = "pin-utils" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" - -[[package]] -name = "pio" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76e09694b50f89f302ed531c1f2a7569f0be5867aee4ab4f8f729bbeec0078e3" -dependencies = [ - "arrayvec", - "num_enum", - "paste", -] - -[[package]] -name = "pio-parser" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77532c2b8279aef98dfc7207ef15298a5a3d6b6cc76ccc8b65913d69f3a8dd6b" -dependencies = [ - "lalrpop", - "lalrpop-util", - "pio", - "regex-syntax 0.6.29", -] - -[[package]] -name = "pio-proc" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b04dc870fb3a4fd8b3e4ca8c61b53bc8ac4eb78b66805d2b3c2e5c4829e0d7a" -dependencies = [ - "codespan-reporting", - "lalrpop-util", - "pio", - "pio-parser", - "proc-macro-error", - "proc-macro2", - "quote", - "regex-syntax 0.6.29", - "syn 1.0.109", -] - -[[package]] -name = "precomputed-hash" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" - -[[package]] -name = "proc-macro-error" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" -dependencies = [ - "proc-macro-error-attr", - "proc-macro2", - "quote", - "syn 1.0.109", - "version_check", -] - -[[package]] -name = "proc-macro-error-attr" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" -dependencies = [ - "proc-macro2", - "quote", - "version_check", -] - -[[package]] -name = "proc-macro2" -version = "1.0.59" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6aeca18b86b413c660b781aa319e4e2648a3e6f9eadc9b47e9038e6fe9f3451b" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "quote" -version = "1.0.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b9ab9c7eadfd8df19006f1cf1a4aed13540ed5cbc047010ece5826e10825488" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "rand_core" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" - -[[package]] -name = "redox_syscall" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" -dependencies = [ - "bitflags", -] - -[[package]] -name = "redox_users" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" -dependencies = [ - "getrandom", - "redox_syscall", - "thiserror", -] - -[[package]] -name = "regex" -version = "1.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81ca098a9821bd52d6b24fd8b10bd081f47d39c22778cafaa75a2857a62c6390" -dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax 0.7.2", -] - -[[package]] -name = "regex-syntax" -version = "0.6.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" - -[[package]] -name = "regex-syntax" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "436b050e76ed2903236f032a59761c1eb99e1b0aead2c257922771dab1fc8c78" - -[[package]] -name = "rp-pac" -version = "4.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a76e426cd8377db668fba1fe885028788b126b7cef91059cd478de8b076c2915" -dependencies = [ - "cortex-m", - "cortex-m-rt", -] - -[[package]] -name = "rp2040-boot2" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c92f344f63f950ee36cf4080050e4dce850839b9175da38f9d2ffb69b4dbb21" -dependencies = [ - "crc-any", -] - -[[package]] -name = "rustc_version" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" -dependencies = [ - "semver 0.9.0", -] - -[[package]] -name = "rustc_version" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" -dependencies = [ - "semver 1.0.17", -] - -[[package]] -name = "rustix" -version = "0.37.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acf8729d8542766f1b2cf77eb034d52f40d375bb8b615d0b147089946e16613d" -dependencies = [ - "bitflags", - "errno", - "io-lifetimes", - "libc", - "linux-raw-sys", - "windows-sys 0.48.0", -] - -[[package]] -name = "rustversion" -version = "1.0.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f3208ce4d8448b3f3e7d168a73f5e0c43a61e32930de3bceeccedb388b6bf06" - -[[package]] -name = "scopeguard" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" - -[[package]] -name = "semver" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" -dependencies = [ - "semver-parser", -] - -[[package]] -name = "semver" -version = "1.0.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bebd363326d05ec3e2f532ab7660680f3b02130d780c299bca73469d521bc0ed" - -[[package]] -name = "semver-parser" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" - -[[package]] -name = "siphasher" -version = "0.3.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7bd3e3206899af3f8b12af284fafc038cc1dc2b41d1b89dd17297221c5d225de" - -[[package]] -name = "smallvec" -version = "1.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" - -[[package]] -name = "smoltcp" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e9786ac45091b96f946693e05bfa4d8ca93e2d3341237d97a380107a6b38dea" -dependencies = [ - "bitflags", - "byteorder", - "cfg-if", - "defmt", - "heapless", - "managed", -] - -[[package]] -name = "spin" -version = "0.9.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" -dependencies = [ - "lock_api", -] - -[[package]] -name = "stable_deref_trait" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" - -[[package]] -name = "static_cell" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4c37c250d21f53fa7165e76e5401d7e6539c211a8d2cf449e3962956a5cc2ce" -dependencies = [ - "atomic-polyfill 1.0.2", -] - -[[package]] -name = "string_cache" -version = "0.8.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f91138e76242f575eb1d3b38b4f1362f10d3a43f47d182a5b359af488a02293b" -dependencies = [ - "new_debug_unreachable", - "once_cell", - "parking_lot", - "phf_shared", - "precomputed-hash", -] - -[[package]] -name = "strsim" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" - -[[package]] -name = "syn" -version = "1.0.109" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "syn" -version = "2.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32d41677bcbe24c20c52e7c70b0d8db04134c5d1066bf98662e2871ad200ea3e" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "term" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c59df8ac95d96ff9bede18eb7300b0fda5e5d8d90960e76f8e14ae765eedbf1f" -dependencies = [ - "dirs-next", - "rustversion", - "winapi", -] - -[[package]] -name = "termcolor" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6" -dependencies = [ - "winapi-util", -] - -[[package]] -name = "thiserror" -version = "1.0.40" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac" -dependencies = [ - "thiserror-impl", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.40" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.18", -] - -[[package]] -name = "tiny-keccak" -version = "2.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" -dependencies = [ - "crunchy", -] - -[[package]] -name = "typenum" -version = "1.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" - -[[package]] -name = "unicode-ident" -version = "1.0.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b15811caf2415fb889178633e7724bad2509101cde276048e013b9def5e51fa0" - -[[package]] -name = "unicode-width" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" - -[[package]] -name = "unicode-xid" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" - -[[package]] -name = "vcell" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77439c1b53d2303b20d9459b1ade71a83c716e3f9c34f3228c00e6f185d6c002" - -[[package]] -name = "version_check" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" - -[[package]] -name = "void" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" - -[[package]] -name = "volatile-register" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ee8f19f9d74293faf70901bc20ad067dc1ad390d2cbf1e3f75f721ffee908b6" -dependencies = [ - "vcell", -] - -[[package]] -name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" - -[[package]] -name = "winapi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" -dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", -] - -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" - -[[package]] -name = "winapi-util" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" -dependencies = [ - "winapi", -] - -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" - -[[package]] -name = "windows-sys" -version = "0.45.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" -dependencies = [ - "windows-targets 0.42.2", -] - -[[package]] -name = "windows-sys" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" -dependencies = [ - "windows-targets 0.48.0", -] - -[[package]] -name = "windows-targets" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" -dependencies = [ - "windows_aarch64_gnullvm 0.42.2", - "windows_aarch64_msvc 0.42.2", - "windows_i686_gnu 0.42.2", - "windows_i686_msvc 0.42.2", - "windows_x86_64_gnu 0.42.2", - "windows_x86_64_gnullvm 0.42.2", - "windows_x86_64_msvc 0.42.2", -] - -[[package]] -name = "windows-targets" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" -dependencies = [ - "windows_aarch64_gnullvm 0.48.0", - "windows_aarch64_msvc 0.48.0", - "windows_i686_gnu 0.48.0", - "windows_i686_msvc 0.48.0", - "windows_x86_64_gnu 0.48.0", - "windows_x86_64_gnullvm 0.48.0", - "windows_x86_64_msvc 0.48.0", -] - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" - -[[package]] -name = "windows_i686_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" - -[[package]] -name = "windows_i686_gnu" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" - -[[package]] -name = "windows_i686_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" - -[[package]] -name = "windows_i686_msvc" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" diff --git a/examples/rpi-pico-w/Cargo.toml b/examples/rpi-pico-w/Cargo.toml deleted file mode 100644 index d3ce3085..00000000 --- a/examples/rpi-pico-w/Cargo.toml +++ /dev/null @@ -1,67 +0,0 @@ -[package] -name = "cyw43-example-rpi-pico-w" -version = "0.1.0" -edition = "2021" - - -[dependencies] -cyw43 = { path = "../../cyw43", features = ["defmt", "firmware-logs"] } -cyw43-pio = { path = "../../cyw43-pio", features = ["defmt", "overclock"] } -embassy-executor = { version = "0.2.0", features = ["defmt", "integrated-timers", "executor-thread", "arch-cortex-m"] } -embassy-time = { version = "0.1.0", features = ["defmt", "defmt-timestamp-uptime"] } -embassy-rp = { version = "0.1.0", features = ["defmt", "unstable-traits", "nightly", "unstable-pac", "time-driver"] } -embassy-net = { version = "0.1.0", features = ["defmt", "tcp", "dhcpv4", "medium-ethernet", "unstable-traits", "nightly"] } -atomic-polyfill = "0.1.5" -static_cell = "1.0" - -defmt = "0.3.4" -defmt-rtt = "0.3" -panic-probe = { version = "0.3", features = ["print-defmt"] } - -cortex-m = { version = "0.7.6", features = ["critical-section-single-core"] } -cortex-m-rt = "0.7.0" -futures = { version = "0.3.17", default-features = false, features = ["async-await", "cfg-target-has-atomic", "unstable"] } - -embedded-io = { version = "0.4.0", features = ["async", "defmt"] } -heapless = "0.7.15" - - -[patch.crates-io] -embassy-executor = { git = "https://github.com/embassy-rs/embassy", rev = "82f7e104d90a6628d1873017ea5ef6a7afb3b3f7" } -embassy-time = { git = "https://github.com/embassy-rs/embassy", rev = "82f7e104d90a6628d1873017ea5ef6a7afb3b3f7" } -embassy-futures = { git = "https://github.com/embassy-rs/embassy", rev = "82f7e104d90a6628d1873017ea5ef6a7afb3b3f7" } -embassy-sync = { git = "https://github.com/embassy-rs/embassy", rev = "82f7e104d90a6628d1873017ea5ef6a7afb3b3f7" } -embassy-rp = { git = "https://github.com/embassy-rs/embassy", rev = "82f7e104d90a6628d1873017ea5ef6a7afb3b3f7" } -embassy-net = { git = "https://github.com/embassy-rs/embassy", rev = "82f7e104d90a6628d1873017ea5ef6a7afb3b3f7" } -embassy-net-driver = { git = "https://github.com/embassy-rs/embassy", rev = "82f7e104d90a6628d1873017ea5ef6a7afb3b3f7" } -embassy-net-driver-channel = { git = "https://github.com/embassy-rs/embassy", rev = "82f7e104d90a6628d1873017ea5ef6a7afb3b3f7" } - -[profile.dev] -debug = 2 -debug-assertions = true -opt-level = 1 -overflow-checks = true - -[profile.release] -codegen-units = 1 -debug = 1 -debug-assertions = false -incremental = false -lto = 'fat' -opt-level = 's' -overflow-checks = false - -# do not optimize proc-macro crates = faster builds from scratch -[profile.dev.build-override] -codegen-units = 8 -debug = false -debug-assertions = false -opt-level = 0 -overflow-checks = false - -[profile.release.build-override] -codegen-units = 8 -debug = false -debug-assertions = false -opt-level = 0 -overflow-checks = false diff --git a/examples/rpi-pico-w/build.rs b/examples/rpi-pico-w/build.rs deleted file mode 100644 index 3f915f93..00000000 --- a/examples/rpi-pico-w/build.rs +++ /dev/null @@ -1,36 +0,0 @@ -//! This build script copies the `memory.x` file from the crate root into -//! a directory where the linker can always find it at build time. -//! For many projects this is optional, as the linker always searches the -//! project root directory -- wherever `Cargo.toml` is. However, if you -//! are using a workspace or have a more complicated build setup, this -//! build script becomes required. Additionally, by requesting that -//! Cargo re-run the build script whenever `memory.x` is changed, -//! updating `memory.x` ensures a rebuild of the application with the -//! new memory settings. - -use std::env; -use std::fs::File; -use std::io::Write; -use std::path::PathBuf; - -fn main() { - // Put `memory.x` in our output directory and ensure it's - // on the linker search path. - let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap()); - File::create(out.join("memory.x")) - .unwrap() - .write_all(include_bytes!("memory.x")) - .unwrap(); - println!("cargo:rustc-link-search={}", out.display()); - - // By default, Cargo will re-run a build script whenever - // any file in the project changes. By specifying `memory.x` - // here, we ensure the build script is only re-run when - // `memory.x` is changed. - println!("cargo:rerun-if-changed=memory.x"); - - println!("cargo:rustc-link-arg-bins=--nmagic"); - println!("cargo:rustc-link-arg-bins=-Tlink.x"); - println!("cargo:rustc-link-arg-bins=-Tlink-rp.x"); - println!("cargo:rustc-link-arg-bins=-Tdefmt.x"); -} diff --git a/examples/rpi-pico-w/memory.x b/examples/rpi-pico-w/memory.x deleted file mode 100644 index eb8c1731..00000000 --- a/examples/rpi-pico-w/memory.x +++ /dev/null @@ -1,5 +0,0 @@ -MEMORY { - BOOT2 : ORIGIN = 0x10000000, LENGTH = 0x100 - FLASH : ORIGIN = 0x10000100, LENGTH = 1024K - 0x100 - RAM : ORIGIN = 0x20000000, LENGTH = 256K -} \ No newline at end of file From 7f0e778145e7a0f7281d9b37e59473fddf232097 Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Wed, 31 May 2023 00:54:20 +0200 Subject: [PATCH 176/178] move embassy-net-w5500 to subdir. --- .github/workflows/rust.yml | 29 --- .gitignore | 4 - .vscode/settings.json | 13 -- LICENSE-APACHE | 201 ------------------ LICENSE-MIT | 25 --- ci.sh | 18 -- Cargo.toml => embassy-net-w5500/Cargo.toml | 0 README.md => embassy-net-w5500/README.md | 0 {src => embassy-net-w5500/src}/device.rs | 0 {src => embassy-net-w5500/src}/lib.rs | 0 {src => embassy-net-w5500/src}/socket.rs | 0 {src => embassy-net-w5500/src}/spi.rs | 0 examples/.cargo/config.toml | 8 - examples/Cargo.toml | 70 ------ examples/README.md | 33 --- examples/build.rs | 36 ---- examples/memory.x | 5 - .../src/bin/ethernet_w5500_multisocket.rs} | 4 + .../src/bin/ethernet_w5500_tcp_client.rs} | 4 + .../src/bin/ethernet_w5500_tcp_server.rs} | 5 + .../src/bin/ethernet_w5500_udp.rs} | 4 + rust-toolchain.toml | 8 - 22 files changed, 17 insertions(+), 450 deletions(-) delete mode 100644 .github/workflows/rust.yml delete mode 100644 .gitignore delete mode 100644 .vscode/settings.json delete mode 100644 LICENSE-APACHE delete mode 100644 LICENSE-MIT delete mode 100755 ci.sh rename Cargo.toml => embassy-net-w5500/Cargo.toml (100%) rename README.md => embassy-net-w5500/README.md (100%) rename {src => embassy-net-w5500/src}/device.rs (100%) rename {src => embassy-net-w5500/src}/lib.rs (100%) rename {src => embassy-net-w5500/src}/socket.rs (100%) rename {src => embassy-net-w5500/src}/spi.rs (100%) delete mode 100644 examples/.cargo/config.toml delete mode 100644 examples/Cargo.toml delete mode 100644 examples/README.md delete mode 100644 examples/build.rs delete mode 100644 examples/memory.x rename examples/{src/bin/multisocket.rs => rp/src/bin/ethernet_w5500_multisocket.rs} (94%) rename examples/{src/bin/tcp-client.rs => rp/src/bin/ethernet_w5500_tcp_client.rs} (93%) rename examples/{src/bin/tcp-server.rs => rp/src/bin/ethernet_w5500_tcp_server.rs} (93%) rename examples/{src/bin/udp.rs => rp/src/bin/ethernet_w5500_udp.rs} (94%) delete mode 100644 rust-toolchain.toml diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml deleted file mode 100644 index dfa96dd0..00000000 --- a/.github/workflows/rust.yml +++ /dev/null @@ -1,29 +0,0 @@ -name: Rust - -on: - push: - branches: [main] - pull_request: - branches: [main] - merge_group: - -env: - CARGO_TERM_COLOR: always - -jobs: - build-nightly: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - uses: actions/cache@v2 - with: - path: | - ~/.cargo/registry - ~/.cargo/git - target - key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} - - - name: Check fmt - run: cargo fmt -- --check - - name: Build - run: ./ci.sh diff --git a/.gitignore b/.gitignore deleted file mode 100644 index a2ac3d82..00000000 --- a/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -*.iml -**/target -**/*.rs.bk -Cargo.lock diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index 231c407a..00000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "editor.formatOnSave": true, - "[toml]": { - "editor.formatOnSave": false - }, - "rust-analyzer.check.allTargets": false, - "rust-analyzer.check.noDefaultFeatures": true, - "rust-analyzer.cargo.noDefaultFeatures": true, - "rust-analyzer.cargo.target": "thumbv6m-none-eabi", - "rust-analyzer.linkedProjects": [ - ".\\examples\\Cargo.toml" - ] -} \ No newline at end of file diff --git a/LICENSE-APACHE b/LICENSE-APACHE deleted file mode 100644 index ea4fa15c..00000000 --- a/LICENSE-APACHE +++ /dev/null @@ -1,201 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - -TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - -1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - -2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - -3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - -4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - -5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - -6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - -7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - -8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - -9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - -END OF TERMS AND CONDITIONS - -APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - -Copyright 2019-2022 Embassy project contributors - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. diff --git a/LICENSE-MIT b/LICENSE-MIT deleted file mode 100644 index 87c05283..00000000 --- a/LICENSE-MIT +++ /dev/null @@ -1,25 +0,0 @@ -Copyright (c) 2019-2022 Embassy project contributors - -Permission is hereby granted, free of charge, to any -person obtaining a copy of this software and associated -documentation files (the "Software"), to deal in the -Software without restriction, including without -limitation the rights to use, copy, modify, merge, -publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software -is furnished to do so, subject to the following -conditions: - -The above copyright notice and this permission notice -shall be included in all copies or substantial portions -of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF -ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED -TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A -PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT -SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR -IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -DEALINGS IN THE SOFTWARE. diff --git a/ci.sh b/ci.sh deleted file mode 100755 index 0a287698..00000000 --- a/ci.sh +++ /dev/null @@ -1,18 +0,0 @@ -#!/bin/bash - -set -euxo pipefail - -export DEFMT_LOG=trace - -# build examples -#================== - -(cd examples; cargo build --bin multisocket --release) -(cd examples; cargo build --bin tcp-client --release) -(cd examples; cargo build --bin tcp-server --release) -(cd examples; cargo build --bin udp --release) - -# build lib -#============ - -cargo build --target thumbv6m-none-eabi diff --git a/Cargo.toml b/embassy-net-w5500/Cargo.toml similarity index 100% rename from Cargo.toml rename to embassy-net-w5500/Cargo.toml diff --git a/README.md b/embassy-net-w5500/README.md similarity index 100% rename from README.md rename to embassy-net-w5500/README.md diff --git a/src/device.rs b/embassy-net-w5500/src/device.rs similarity index 100% rename from src/device.rs rename to embassy-net-w5500/src/device.rs diff --git a/src/lib.rs b/embassy-net-w5500/src/lib.rs similarity index 100% rename from src/lib.rs rename to embassy-net-w5500/src/lib.rs diff --git a/src/socket.rs b/embassy-net-w5500/src/socket.rs similarity index 100% rename from src/socket.rs rename to embassy-net-w5500/src/socket.rs diff --git a/src/spi.rs b/embassy-net-w5500/src/spi.rs similarity index 100% rename from src/spi.rs rename to embassy-net-w5500/src/spi.rs diff --git a/examples/.cargo/config.toml b/examples/.cargo/config.toml deleted file mode 100644 index e6b6b4a4..00000000 --- a/examples/.cargo/config.toml +++ /dev/null @@ -1,8 +0,0 @@ -[target.'cfg(all(target_arch = "arm", target_os = "none"))'] -runner = "probe-rs-cli run --chip RP2040" - -[build] -target = "thumbv6m-none-eabi" - -[env] -DEFMT_LOG = "info" diff --git a/examples/Cargo.toml b/examples/Cargo.toml deleted file mode 100644 index 46659c2b..00000000 --- a/examples/Cargo.toml +++ /dev/null @@ -1,70 +0,0 @@ -[package] -name = "embassy-net-w5500-examples" -version = "0.1.0" -edition = "2021" - -[dependencies] -embassy-executor = { version = "0.2.0", features = ["defmt", "integrated-timers", "executor-thread", "arch-cortex-m"] } -embassy-time = { version = "0.1.0", features = ["defmt", "defmt-timestamp-uptime"] } -embassy-rp = { version = "0.1.0", features = ["defmt", "unstable-traits", "nightly", "unstable-pac", "time-driver"] } -embassy-net = { version = "0.1.0", features = ["defmt", "tcp", "udp", "dhcpv4", "medium-ethernet", "unstable-traits", "nightly"] } -embassy-sync = { version = "0.1.0" } -embassy-futures = { version = "0.1.0" } -embassy-net-driver = { version = "0.1.0" } -embassy-net-driver-channel = { version = "0.1.0" } -atomic-polyfill = "0.1.5" -static_cell = "1.0" - -defmt = "=0.3.2" -defmt-rtt = "0.3" -panic-probe = { version = "0.3", features = ["print-defmt"] } -cortex-m = { version = "0.7.6", features = ["critical-section-single-core"] } -cortex-m-rt = "0.7.0" - -embedded-io = { version = "0.4.0", features = ["async", "defmt"] } -heapless = "0.7.15" -embedded-hal = { version = "1.0.0-alpha.10" } -embedded-hal-async = { version = "=0.2.0-alpha.1" } -rand = { version = "0.8.5", default-features = false } - -embassy-net-w5500 = { path = "../" } - -[patch.crates-io] -embassy-executor = { git = "https://github.com/embassy-rs/embassy", rev = "e179e7cf85810f0aa7ef8027d8d48f6d21f64dac" } -embassy-time = { git = "https://github.com/embassy-rs/embassy", rev = "e179e7cf85810f0aa7ef8027d8d48f6d21f64dac" } -embassy-futures = { git = "https://github.com/embassy-rs/embassy", rev = "e179e7cf85810f0aa7ef8027d8d48f6d21f64dac" } -embassy-sync = { git = "https://github.com/embassy-rs/embassy", rev = "e179e7cf85810f0aa7ef8027d8d48f6d21f64dac" } -embassy-rp = { git = "https://github.com/embassy-rs/embassy", rev = "e179e7cf85810f0aa7ef8027d8d48f6d21f64dac" } -embassy-net = { git = "https://github.com/embassy-rs/embassy", rev = "e179e7cf85810f0aa7ef8027d8d48f6d21f64dac" } -embassy-net-driver = { git = "https://github.com/embassy-rs/embassy", rev = "e179e7cf85810f0aa7ef8027d8d48f6d21f64dac" } -embassy-net-driver-channel = { git = "https://github.com/embassy-rs/embassy", rev = "e179e7cf85810f0aa7ef8027d8d48f6d21f64dac" } - -[profile.dev] -debug = 2 -debug-assertions = true -opt-level = 1 -overflow-checks = true - -[profile.release] -codegen-units = 1 -debug = 1 -debug-assertions = false -incremental = false -lto = 'fat' -opt-level = 'z' -overflow-checks = false - -# do not optimize proc-macro crates = faster builds from scratch -[profile.dev.build-override] -codegen-units = 8 -debug = false -debug-assertions = false -opt-level = 0 -overflow-checks = false - -[profile.release.build-override] -codegen-units = 8 -debug = false -debug-assertions = false -opt-level = 0 -overflow-checks = false diff --git a/examples/README.md b/examples/README.md deleted file mode 100644 index d818c4a8..00000000 --- a/examples/README.md +++ /dev/null @@ -1,33 +0,0 @@ -# Examples for the rp2040 `WIZnet W5500-EVB-Pico` board - -Examples are written for the [`WIZnet W5500-EVB-Pico`](https://www.wiznet.io/product-item/w5500-evb-pico/) board. - -## Prerequisites -```bash -cargo install probe-rs-cli -``` - -## TCP server example -```bash -cargo run --bin tcp-server --release -``` -This example implements a TCP echo server on port 1234 and using DHCP. -Send it some data, you should see it echoed back and printed in the console. - -## Multi-socket example -```bash -cargo run --bin multisocket --release -``` -This example shows how you can allow multiple simultaneous TCP connections, by having multiple sockets listening on the same port. - -## TCP client example -```bash -cargo run --bin tcp-client --release -``` -This example implements a TCP client that attempts to connect to a host on port 1234 and send it some data once per second. - -## UDP server example -```bash -cargo run --bin udp --release -``` -This example implements a UDP server listening on port 1234 and echoing back the data. diff --git a/examples/build.rs b/examples/build.rs deleted file mode 100644 index 3f915f93..00000000 --- a/examples/build.rs +++ /dev/null @@ -1,36 +0,0 @@ -//! This build script copies the `memory.x` file from the crate root into -//! a directory where the linker can always find it at build time. -//! For many projects this is optional, as the linker always searches the -//! project root directory -- wherever `Cargo.toml` is. However, if you -//! are using a workspace or have a more complicated build setup, this -//! build script becomes required. Additionally, by requesting that -//! Cargo re-run the build script whenever `memory.x` is changed, -//! updating `memory.x` ensures a rebuild of the application with the -//! new memory settings. - -use std::env; -use std::fs::File; -use std::io::Write; -use std::path::PathBuf; - -fn main() { - // Put `memory.x` in our output directory and ensure it's - // on the linker search path. - let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap()); - File::create(out.join("memory.x")) - .unwrap() - .write_all(include_bytes!("memory.x")) - .unwrap(); - println!("cargo:rustc-link-search={}", out.display()); - - // By default, Cargo will re-run a build script whenever - // any file in the project changes. By specifying `memory.x` - // here, we ensure the build script is only re-run when - // `memory.x` is changed. - println!("cargo:rerun-if-changed=memory.x"); - - println!("cargo:rustc-link-arg-bins=--nmagic"); - println!("cargo:rustc-link-arg-bins=-Tlink.x"); - println!("cargo:rustc-link-arg-bins=-Tlink-rp.x"); - println!("cargo:rustc-link-arg-bins=-Tdefmt.x"); -} diff --git a/examples/memory.x b/examples/memory.x deleted file mode 100644 index eb8c1731..00000000 --- a/examples/memory.x +++ /dev/null @@ -1,5 +0,0 @@ -MEMORY { - BOOT2 : ORIGIN = 0x10000000, LENGTH = 0x100 - FLASH : ORIGIN = 0x10000100, LENGTH = 1024K - 0x100 - RAM : ORIGIN = 0x20000000, LENGTH = 256K -} \ No newline at end of file diff --git a/examples/src/bin/multisocket.rs b/examples/rp/src/bin/ethernet_w5500_multisocket.rs similarity index 94% rename from examples/src/bin/multisocket.rs rename to examples/rp/src/bin/ethernet_w5500_multisocket.rs index 49bcbdbb..eb3b8de8 100644 --- a/examples/src/bin/multisocket.rs +++ b/examples/rp/src/bin/ethernet_w5500_multisocket.rs @@ -1,3 +1,7 @@ +//! This example shows how you can allow multiple simultaneous TCP connections, by having multiple sockets listening on the same port. +//! +//! Example written for the [`WIZnet W5500-EVB-Pico`](https://www.wiznet.io/product-item/w5500-evb-pico/) board. + #![no_std] #![no_main] #![feature(type_alias_impl_trait)] diff --git a/examples/src/bin/tcp-client.rs b/examples/rp/src/bin/ethernet_w5500_tcp_client.rs similarity index 93% rename from examples/src/bin/tcp-client.rs rename to examples/rp/src/bin/ethernet_w5500_tcp_client.rs index 32dfb6a6..e166e0f3 100644 --- a/examples/src/bin/tcp-client.rs +++ b/examples/rp/src/bin/ethernet_w5500_tcp_client.rs @@ -1,3 +1,7 @@ +//! This example implements a TCP client that attempts to connect to a host on port 1234 and send it some data once per second. +//! +//! Example written for the [`WIZnet W5500-EVB-Pico`](https://www.wiznet.io/product-item/w5500-evb-pico/) board. + #![no_std] #![no_main] #![feature(type_alias_impl_trait)] diff --git a/examples/src/bin/tcp-server.rs b/examples/rp/src/bin/ethernet_w5500_tcp_server.rs similarity index 93% rename from examples/src/bin/tcp-server.rs rename to examples/rp/src/bin/ethernet_w5500_tcp_server.rs index 04b22014..ffd664d1 100644 --- a/examples/src/bin/tcp-server.rs +++ b/examples/rp/src/bin/ethernet_w5500_tcp_server.rs @@ -1,3 +1,8 @@ +//! This example implements a TCP echo server on port 1234 and using DHCP. +//! Send it some data, you should see it echoed back and printed in the console. +//! +//! Example written for the [`WIZnet W5500-EVB-Pico`](https://www.wiznet.io/product-item/w5500-evb-pico/) board. + #![no_std] #![no_main] #![feature(type_alias_impl_trait)] diff --git a/examples/src/bin/udp.rs b/examples/rp/src/bin/ethernet_w5500_udp.rs similarity index 94% rename from examples/src/bin/udp.rs rename to examples/rp/src/bin/ethernet_w5500_udp.rs index 4dc5e1f2..08ffeb24 100644 --- a/examples/src/bin/udp.rs +++ b/examples/rp/src/bin/ethernet_w5500_udp.rs @@ -1,3 +1,7 @@ +//! This example implements a UDP server listening on port 1234 and echoing back the data. +//! +//! Example written for the [`WIZnet W5500-EVB-Pico`](https://www.wiznet.io/product-item/w5500-evb-pico/) board. + #![no_std] #![no_main] #![feature(type_alias_impl_trait)] diff --git a/rust-toolchain.toml b/rust-toolchain.toml deleted file mode 100644 index 2582e88f..00000000 --- a/rust-toolchain.toml +++ /dev/null @@ -1,8 +0,0 @@ -# Before upgrading check that everything is available on all tier1 targets here: -# https://rust-lang.github.io/rustup-components-history -[toolchain] -channel = "nightly-2023-04-18" -components = [ "rust-src", "rustfmt" ] -targets = [ - "thumbv6m-none-eabi", -] From d70994e4a8ea695f07b777fa99d7db4e5d4a7122 Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Wed, 31 May 2023 01:01:30 +0200 Subject: [PATCH 177/178] net-w5500: integrate into main repo. --- embassy-net-w5500/Cargo.toml | 12 +---- embassy-net-w5500/src/device.rs | 33 ++++-------- embassy-net-w5500/src/lib.rs | 12 ++--- embassy-net-w5500/src/socket.rs | 53 ++++++------------- embassy-net-w5500/src/spi.rs | 17 ++---- examples/rp/Cargo.toml | 4 +- .../rp/src/bin/ethernet_w5500_multisocket.rs | 25 +++------ .../rp/src/bin/ethernet_w5500_tcp_client.rs | 13 ++--- .../rp/src/bin/ethernet_w5500_tcp_server.rs | 13 ++--- examples/rp/src/bin/ethernet_w5500_udp.rs | 22 ++------ 10 files changed, 55 insertions(+), 149 deletions(-) diff --git a/embassy-net-w5500/Cargo.toml b/embassy-net-w5500/Cargo.toml index 1921e812..3f19e3d3 100644 --- a/embassy-net-w5500/Cargo.toml +++ b/embassy-net-w5500/Cargo.toml @@ -10,17 +10,7 @@ edition = "2021" [dependencies] embedded-hal = { version = "1.0.0-alpha.10" } embedded-hal-async = { version = "=0.2.0-alpha.1" } -embassy-net-driver-channel = { version = "0.1.0" } +embassy-net-driver-channel = { version = "0.1.0", path = "../embassy-net-driver-channel"} embassy-time = { version = "0.1.0" } embassy-futures = { version = "0.1.0" } defmt = { version = "0.3", optional = true } - -[patch.crates-io] -embassy-executor = { git = "https://github.com/embassy-rs/embassy", rev = "e179e7cf85810f0aa7ef8027d8d48f6d21f64dac" } -embassy-time = { git = "https://github.com/embassy-rs/embassy", rev = "e179e7cf85810f0aa7ef8027d8d48f6d21f64dac" } -embassy-futures = { git = "https://github.com/embassy-rs/embassy", rev = "e179e7cf85810f0aa7ef8027d8d48f6d21f64dac" } -embassy-sync = { git = "https://github.com/embassy-rs/embassy", rev = "e179e7cf85810f0aa7ef8027d8d48f6d21f64dac" } -embassy-rp = { git = "https://github.com/embassy-rs/embassy", rev = "e179e7cf85810f0aa7ef8027d8d48f6d21f64dac" } -embassy-net = { git = "https://github.com/embassy-rs/embassy", rev = "e179e7cf85810f0aa7ef8027d8d48f6d21f64dac" } -embassy-net-driver = { git = "https://github.com/embassy-rs/embassy", rev = "e179e7cf85810f0aa7ef8027d8d48f6d21f64dac" } -embassy-net-driver-channel = { git = "https://github.com/embassy-rs/embassy", rev = "e179e7cf85810f0aa7ef8027d8d48f6d21f64dac" } diff --git a/embassy-net-w5500/src/device.rs b/embassy-net-w5500/src/device.rs index 8158bc98..9874df0d 100644 --- a/embassy-net-w5500/src/device.rs +++ b/embassy-net-w5500/src/device.rs @@ -1,6 +1,7 @@ +use embedded_hal_async::spi::SpiDevice; + use crate::socket; use crate::spi::SpiInterface; -use embedded_hal_async::spi::SpiDevice; pub const MODE: u16 = 0x00; pub const MAC: u16 = 0x09; @@ -27,12 +28,10 @@ impl W5500 { pub async fn new(spi: SPI, mac_addr: [u8; 6]) -> Result, SPI::Error> { let mut bus = SpiInterface(spi); // Reset device - bus.write_frame(RegisterBlock::Common, MODE, &[0x80]) - .await?; + bus.write_frame(RegisterBlock::Common, MODE, &[0x80]).await?; // Enable interrupt pin - bus.write_frame(RegisterBlock::Common, SOCKET_INTR, &[0x01]) - .await?; + bus.write_frame(RegisterBlock::Common, SOCKET_INTR, &[0x01]).await?; // Enable receive interrupt bus.write_frame( RegisterBlock::Socket0, @@ -42,8 +41,7 @@ impl W5500 { .await?; // Set MAC address - bus.write_frame(RegisterBlock::Common, MAC, &mac_addr) - .await?; + bus.write_frame(RegisterBlock::Common, MAC, &mac_addr).await?; // Set the raw socket RX/TX buffer sizes to 16KB bus.write_frame(RegisterBlock::Socket0, socket::TXBUF_SIZE, &[16]) @@ -53,8 +51,7 @@ impl W5500 { // MACRAW mode with MAC filtering. let mode: u8 = (1 << 2) | (1 << 7); - bus.write_frame(RegisterBlock::Socket0, socket::MODE, &[mode]) - .await?; + bus.write_frame(RegisterBlock::Socket0, socket::MODE, &[mode]).await?; socket::command(&mut bus, socket::Command::Open).await?; Ok(Self { bus }) @@ -70,17 +67,9 @@ impl W5500 { &mut buffer[..rx_size - offset as usize] }; - let read_ptr = socket::get_rx_read_ptr(&mut self.bus) - .await? - .wrapping_add(offset); - self.bus - .read_frame(RegisterBlock::RxBuf, read_ptr, read_buffer) - .await?; - socket::set_rx_read_ptr( - &mut self.bus, - read_ptr.wrapping_add(read_buffer.len() as u16), - ) - .await?; + let read_ptr = socket::get_rx_read_ptr(&mut self.bus).await?.wrapping_add(offset); + self.bus.read_frame(RegisterBlock::RxBuf, read_ptr, read_buffer).await?; + socket::set_rx_read_ptr(&mut self.bus, read_ptr.wrapping_add(read_buffer.len() as u16)).await?; Ok(read_buffer.len()) } @@ -125,9 +114,7 @@ impl W5500 { pub async fn write_frame(&mut self, frame: &[u8]) -> Result { while socket::get_tx_free_size(&mut self.bus).await? < frame.len() as u16 {} let write_ptr = socket::get_tx_write_ptr(&mut self.bus).await?; - self.bus - .write_frame(RegisterBlock::TxBuf, write_ptr, frame) - .await?; + self.bus.write_frame(RegisterBlock::TxBuf, write_ptr, frame).await?; socket::set_tx_write_ptr(&mut self.bus, write_ptr.wrapping_add(frame.len() as u16)).await?; socket::command(&mut self.bus, socket::Command::Send).await?; Ok(frame.len()) diff --git a/embassy-net-w5500/src/lib.rs b/embassy-net-w5500/src/lib.rs index bf14b05b..6821373e 100644 --- a/embassy-net-w5500/src/lib.rs +++ b/embassy-net-w5500/src/lib.rs @@ -4,7 +4,6 @@ mod device; mod socket; mod spi; -use crate::device::W5500; use embassy_futures::select::{select, Either}; use embassy_net_driver_channel as ch; use embassy_net_driver_channel::driver::LinkState; @@ -12,6 +11,8 @@ use embassy_time::{Duration, Timer}; use embedded_hal::digital::OutputPin; use embedded_hal_async::digital::Wait; use embedded_hal_async::spi::SpiDevice; + +use crate::device::W5500; const MTU: usize = 1514; /// Type alias for the embassy-net driver for W5500 @@ -77,14 +78,7 @@ impl<'d, SPI: SpiDevice, INT: Wait, RST: OutputPin> Runner<'d, SPI, INT, RST> { } /// Obtain a driver for using the W5500 with [`embassy-net`](crates.io/crates/embassy-net). -pub async fn new< - 'a, - const N_RX: usize, - const N_TX: usize, - SPI: SpiDevice, - INT: Wait, - RST: OutputPin, ->( +pub async fn new<'a, const N_RX: usize, const N_TX: usize, SPI: SpiDevice, INT: Wait, RST: OutputPin>( mac_addr: [u8; 6], state: &'a mut State, spi_dev: SPI, diff --git a/embassy-net-w5500/src/socket.rs b/embassy-net-w5500/src/socket.rs index 3f64d04d..3d65583c 100644 --- a/embassy-net-w5500/src/socket.rs +++ b/embassy-net-w5500/src/socket.rs @@ -1,6 +1,7 @@ +use embedded_hal_async::spi::SpiDevice; + use crate::device::RegisterBlock; use crate::spi::SpiInterface; -use embedded_hal_async::spi::SpiDevice; pub const MODE: u16 = 0x00; pub const COMMAND: u16 = 0x01; @@ -25,79 +26,55 @@ pub enum Interrupt { Receive = 0b00100_u8, } -pub async fn reset_interrupt( - bus: &mut SpiInterface, - code: Interrupt, -) -> Result<(), SPI::Error> { +pub async fn reset_interrupt(bus: &mut SpiInterface, code: Interrupt) -> Result<(), SPI::Error> { let data = [code as u8]; bus.write_frame(RegisterBlock::Socket0, INTR, &data).await } -pub async fn get_tx_write_ptr( - bus: &mut SpiInterface, -) -> Result { +pub async fn get_tx_write_ptr(bus: &mut SpiInterface) -> Result { let mut data = [0u8; 2]; bus.read_frame(RegisterBlock::Socket0, TX_DATA_WRITE_PTR, &mut data) .await?; Ok(u16::from_be_bytes(data)) } -pub async fn set_tx_write_ptr( - bus: &mut SpiInterface, - ptr: u16, -) -> Result<(), SPI::Error> { +pub async fn set_tx_write_ptr(bus: &mut SpiInterface, ptr: u16) -> Result<(), SPI::Error> { let data = ptr.to_be_bytes(); - bus.write_frame(RegisterBlock::Socket0, TX_DATA_WRITE_PTR, &data) - .await + bus.write_frame(RegisterBlock::Socket0, TX_DATA_WRITE_PTR, &data).await } -pub async fn get_rx_read_ptr( - bus: &mut SpiInterface, -) -> Result { +pub async fn get_rx_read_ptr(bus: &mut SpiInterface) -> Result { let mut data = [0u8; 2]; bus.read_frame(RegisterBlock::Socket0, RX_DATA_READ_PTR, &mut data) .await?; Ok(u16::from_be_bytes(data)) } -pub async fn set_rx_read_ptr( - bus: &mut SpiInterface, - ptr: u16, -) -> Result<(), SPI::Error> { +pub async fn set_rx_read_ptr(bus: &mut SpiInterface, ptr: u16) -> Result<(), SPI::Error> { let data = ptr.to_be_bytes(); - bus.write_frame(RegisterBlock::Socket0, RX_DATA_READ_PTR, &data) - .await + bus.write_frame(RegisterBlock::Socket0, RX_DATA_READ_PTR, &data).await } -pub async fn command( - bus: &mut SpiInterface, - command: Command, -) -> Result<(), SPI::Error> { +pub async fn command(bus: &mut SpiInterface, command: Command) -> Result<(), SPI::Error> { let data = [command as u8]; - bus.write_frame(RegisterBlock::Socket0, COMMAND, &data) - .await + bus.write_frame(RegisterBlock::Socket0, COMMAND, &data).await } pub async fn get_rx_size(bus: &mut SpiInterface) -> Result { loop { // Wait until two sequential reads are equal let mut res0 = [0u8; 2]; - bus.read_frame(RegisterBlock::Socket0, RECVD_SIZE, &mut res0) - .await?; + bus.read_frame(RegisterBlock::Socket0, RECVD_SIZE, &mut res0).await?; let mut res1 = [0u8; 2]; - bus.read_frame(RegisterBlock::Socket0, RECVD_SIZE, &mut res1) - .await?; + bus.read_frame(RegisterBlock::Socket0, RECVD_SIZE, &mut res1).await?; if res0 == res1 { break Ok(u16::from_be_bytes(res0)); } } } -pub async fn get_tx_free_size( - bus: &mut SpiInterface, -) -> Result { +pub async fn get_tx_free_size(bus: &mut SpiInterface) -> Result { let mut data = [0; 2]; - bus.read_frame(RegisterBlock::Socket0, TX_FREE_SIZE, &mut data) - .await?; + bus.read_frame(RegisterBlock::Socket0, TX_FREE_SIZE, &mut data).await?; Ok(u16::from_be_bytes(data)) } diff --git a/embassy-net-w5500/src/spi.rs b/embassy-net-w5500/src/spi.rs index 55d31188..6cd52c44 100644 --- a/embassy-net-w5500/src/spi.rs +++ b/embassy-net-w5500/src/spi.rs @@ -1,17 +1,13 @@ -use crate::device::RegisterBlock; use embedded_hal_async::spi::{Operation, SpiDevice}; +use crate::device::RegisterBlock; + #[derive(Debug)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct SpiInterface(pub SPI); impl SpiInterface { - pub async fn read_frame( - &mut self, - block: RegisterBlock, - address: u16, - data: &mut [u8], - ) -> Result<(), SPI::Error> { + pub async fn read_frame(&mut self, block: RegisterBlock, address: u16, data: &mut [u8]) -> Result<(), SPI::Error> { let address_phase = address.to_be_bytes(); let control_phase = [(block as u8) << 3]; let operations = &mut [ @@ -22,12 +18,7 @@ impl SpiInterface { self.0.transaction(operations).await } - pub async fn write_frame( - &mut self, - block: RegisterBlock, - address: u16, - data: &[u8], - ) -> Result<(), SPI::Error> { + pub async fn write_frame(&mut self, block: RegisterBlock, address: u16, data: &[u8]) -> Result<(), SPI::Error> { let address_phase = address.to_be_bytes(); let control_phase = [(block as u8) << 3 | 0b0000_0100]; let data_phase = data; diff --git a/examples/rp/Cargo.toml b/examples/rp/Cargo.toml index f77377a6..58b70191 100644 --- a/examples/rp/Cargo.toml +++ b/examples/rp/Cargo.toml @@ -12,7 +12,8 @@ embassy-executor = { version = "0.2.0", path = "../../embassy-executor", feature embassy-time = { version = "0.1.0", path = "../../embassy-time", features = ["nightly", "unstable-traits", "defmt", "defmt-timestamp-uptime"] } embassy-rp = { version = "0.1.0", path = "../../embassy-rp", features = ["defmt", "unstable-traits", "nightly", "unstable-pac", "time-driver", "critical-section-impl"] } embassy-usb = { version = "0.1.0", path = "../../embassy-usb", features = ["defmt"] } -embassy-net = { version = "0.1.0", path = "../../embassy-net", features = ["defmt", "nightly", "tcp", "dhcpv4", "medium-ethernet"] } +embassy-net = { version = "0.1.0", path = "../../embassy-net", features = ["defmt", "nightly", "tcp", "udp", "dhcpv4", "medium-ethernet"] } +embassy-net-w5500 = { version = "0.1.0", path = "../../embassy-net-w5500", features = ["defmt"] } embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } embassy-usb-logger = { version = "0.1.0", path = "../../embassy-usb-logger" } embassy-lora = { version = "0.1.0", path = "../../embassy-lora", features = ["time", "defmt"] } @@ -48,6 +49,7 @@ static_cell = "1.0.0" log = "0.4" pio-proc = "0.2" pio = "0.2.1" +rand = { version = "0.8.5", default-features = false } [profile.release] debug = true diff --git a/examples/rp/src/bin/ethernet_w5500_multisocket.rs b/examples/rp/src/bin/ethernet_w5500_multisocket.rs index eb3b8de8..c8e6d46a 100644 --- a/examples/rp/src/bin/ethernet_w5500_multisocket.rs +++ b/examples/rp/src/bin/ethernet_w5500_multisocket.rs @@ -15,6 +15,7 @@ use embassy_rp::clocks::RoscRng; use embassy_rp::gpio::{Input, Level, Output, Pull}; use embassy_rp::peripherals::{PIN_17, PIN_20, PIN_21, SPI0}; use embassy_rp::spi::{Async, Config as SpiConfig, Spi}; +use embassy_time::Duration; use embedded_hal_async::spi::ExclusiveDevice; use embedded_io::asynch::Write; use rand::RngCore; @@ -62,14 +63,8 @@ async fn main(spawner: Spawner) { let mac_addr = [0x02, 0x00, 0x00, 0x00, 0x00, 0x00]; let state = singleton!(State::<8, 8>::new()); - let (device, runner) = embassy_net_w5500::new( - mac_addr, - state, - ExclusiveDevice::new(spi, cs), - w5500_int, - w5500_reset, - ) - .await; + let (device, runner) = + embassy_net_w5500::new(mac_addr, state, ExclusiveDevice::new(spi, cs), w5500_int, w5500_reset).await; unwrap!(spawner.spawn(ethernet_task(runner))); // Generate random seed @@ -103,18 +98,14 @@ async fn listen_task(stack: &'static Stack>, id: u8, port: u16) let mut buf = [0; 4096]; loop { let mut socket = embassy_net::tcp::TcpSocket::new(stack, &mut rx_buffer, &mut tx_buffer); - socket.set_timeout(Some(embassy_net::SmolDuration::from_secs(10))); + socket.set_timeout(Some(Duration::from_secs(10))); info!("SOCKET {}: Listening on TCP:{}...", id, port); if let Err(e) = socket.accept(port).await { warn!("accept error: {:?}", e); continue; } - info!( - "SOCKET {}: Received connection from {:?}", - id, - socket.remote_endpoint() - ); + info!("SOCKET {}: Received connection from {:?}", id, socket.remote_endpoint()); loop { let n = match socket.read(&mut buf).await { @@ -128,11 +119,7 @@ async fn listen_task(stack: &'static Stack>, id: u8, port: u16) break; } }; - info!( - "SOCKET {}: rxd {}", - id, - core::str::from_utf8(&buf[..n]).unwrap() - ); + info!("SOCKET {}: rxd {}", id, core::str::from_utf8(&buf[..n]).unwrap()); if let Err(e) = socket.write_all(&buf[..n]).await { warn!("write error: {:?}", e); diff --git a/examples/rp/src/bin/ethernet_w5500_tcp_client.rs b/examples/rp/src/bin/ethernet_w5500_tcp_client.rs index e166e0f3..9a7c3ad1 100644 --- a/examples/rp/src/bin/ethernet_w5500_tcp_client.rs +++ b/examples/rp/src/bin/ethernet_w5500_tcp_client.rs @@ -7,6 +7,7 @@ #![feature(type_alias_impl_trait)] use core::str::FromStr; + use defmt::*; use embassy_executor::Spawner; use embassy_futures::yield_now; @@ -65,14 +66,8 @@ async fn main(spawner: Spawner) { let mac_addr = [0x02, 0x00, 0x00, 0x00, 0x00, 0x00]; let state = singleton!(State::<8, 8>::new()); - let (device, runner) = embassy_net_w5500::new( - mac_addr, - state, - ExclusiveDevice::new(spi, cs), - w5500_int, - w5500_reset, - ) - .await; + let (device, runner) = + embassy_net_w5500::new(mac_addr, state, ExclusiveDevice::new(spi, cs), w5500_int, w5500_reset).await; unwrap!(spawner.spawn(ethernet_task(runner))); // Generate random seed @@ -98,7 +93,7 @@ async fn main(spawner: Spawner) { let mut tx_buffer = [0; 4096]; loop { let mut socket = embassy_net::tcp::TcpSocket::new(stack, &mut rx_buffer, &mut tx_buffer); - socket.set_timeout(Some(embassy_net::SmolDuration::from_secs(10))); + socket.set_timeout(Some(Duration::from_secs(10))); led.set_low(); info!("Connecting..."); diff --git a/examples/rp/src/bin/ethernet_w5500_tcp_server.rs b/examples/rp/src/bin/ethernet_w5500_tcp_server.rs index ffd664d1..f0254324 100644 --- a/examples/rp/src/bin/ethernet_w5500_tcp_server.rs +++ b/examples/rp/src/bin/ethernet_w5500_tcp_server.rs @@ -16,6 +16,7 @@ use embassy_rp::clocks::RoscRng; use embassy_rp::gpio::{Input, Level, Output, Pull}; use embassy_rp::peripherals::{PIN_17, PIN_20, PIN_21, SPI0}; use embassy_rp::spi::{Async, Config as SpiConfig, Spi}; +use embassy_time::Duration; use embedded_hal_async::spi::ExclusiveDevice; use embedded_io::asynch::Write; use rand::RngCore; @@ -64,14 +65,8 @@ async fn main(spawner: Spawner) { let mac_addr = [0x02, 0x00, 0x00, 0x00, 0x00, 0x00]; let state = singleton!(State::<8, 8>::new()); - let (device, runner) = embassy_net_w5500::new( - mac_addr, - state, - ExclusiveDevice::new(spi, cs), - w5500_int, - w5500_reset, - ) - .await; + let (device, runner) = + embassy_net_w5500::new(mac_addr, state, ExclusiveDevice::new(spi, cs), w5500_int, w5500_reset).await; unwrap!(spawner.spawn(ethernet_task(runner))); // Generate random seed @@ -98,7 +93,7 @@ async fn main(spawner: Spawner) { let mut buf = [0; 4096]; loop { let mut socket = embassy_net::tcp::TcpSocket::new(stack, &mut rx_buffer, &mut tx_buffer); - socket.set_timeout(Some(embassy_net::SmolDuration::from_secs(10))); + socket.set_timeout(Some(Duration::from_secs(10))); led.set_low(); info!("Listening on TCP:1234..."); diff --git a/examples/rp/src/bin/ethernet_w5500_udp.rs b/examples/rp/src/bin/ethernet_w5500_udp.rs index 08ffeb24..2c54f711 100644 --- a/examples/rp/src/bin/ethernet_w5500_udp.rs +++ b/examples/rp/src/bin/ethernet_w5500_udp.rs @@ -9,8 +9,8 @@ use defmt::*; use embassy_executor::Spawner; use embassy_futures::yield_now; -use embassy_net::udp::UdpSocket; -use embassy_net::{PacketMetadata, Stack, StackResources}; +use embassy_net::udp::{PacketMetadata, UdpSocket}; +use embassy_net::{Stack, StackResources}; use embassy_net_w5500::*; use embassy_rp::clocks::RoscRng; use embassy_rp::gpio::{Input, Level, Output, Pull}; @@ -62,14 +62,8 @@ async fn main(spawner: Spawner) { let mac_addr = [0x02, 0x00, 0x00, 0x00, 0x00, 0x00]; let state = singleton!(State::<8, 8>::new()); - let (device, runner) = embassy_net_w5500::new( - mac_addr, - state, - ExclusiveDevice::new(spi, cs), - w5500_int, - w5500_reset, - ) - .await; + let (device, runner) = + embassy_net_w5500::new(mac_addr, state, ExclusiveDevice::new(spi, cs), w5500_int, w5500_reset).await; unwrap!(spawner.spawn(ethernet_task(runner))); // Generate random seed @@ -98,13 +92,7 @@ async fn main(spawner: Spawner) { let mut tx_meta = [PacketMetadata::EMPTY; 16]; let mut buf = [0; 4096]; loop { - let mut socket = UdpSocket::new( - stack, - &mut rx_meta, - &mut rx_buffer, - &mut tx_meta, - &mut tx_buffer, - ); + let mut socket = UdpSocket::new(stack, &mut rx_meta, &mut rx_buffer, &mut tx_meta, &mut tx_buffer); socket.bind(1234).unwrap(); loop { From 98398d31f7d9bbda06fbb97804ba6844298710bc Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Wed, 31 May 2023 01:12:08 +0200 Subject: [PATCH 178/178] README fixes. --- README.md | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/README.md b/README.md index b65dcbe1..315d247e 100644 --- a/README.md +++ b/README.md @@ -99,13 +99,6 @@ Examples are found in the `examples/` folder seperated by the chip manufacturer ### Running examples -- Setup git submodules (needed for STM32 examples) - -```bash -git submodule init -git submodule update -``` - - Install `probe-rs-cli` with defmt support. ```bash @@ -123,7 +116,7 @@ cd examples/nrf52840 For example: ```bash -cargo run --bin blinky +cargo run --release --bin blinky ``` ## Developing Embassy with Rust Analyzer based editors