(
+ 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: PioPin,
+ CLK: PioPin,
+ {
+ #[cfg(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 1" // necessary for clkdiv=1.
+ "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"
+ "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"
+ "irq 0 side 0"
+
+ ".wrap"
+ );
+
+ let relocated = RelocatedProgram::new(&program.program);
+
+ 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 = common.make_pio_pin(clk);
+ pin_clk.set_drive_strength(Drive::_12mA);
+ pin_clk.set_slew_rate(SlewRate::Fast);
+
+ 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;
+
+ #[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);
+ }
+
+ #[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);
+
+ sm.set_pin_dirs(Direction::Out, &[&pin_clk, &pin_io]);
+ sm.set_pins(Level::Low, &[&pin_clk, &pin_io]);
+
+ Self {
+ cs,
+ sm,
+ irq,
+ dma: dma.into_ref(),
+ wrap_target: relocated.wrap().target,
+ }
+ }
+
+ 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;
+
+ #[cfg(feature = "defmt")]
+ defmt::trace!("write={} read={}", write_bits, read_bits);
+
+ 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.tx().dma_push(self.dma.reborrow(), write).await;
+
+ let mut status = 0;
+ self.sm
+ .rx()
+ .dma_pull(self.dma.reborrow(), slice::from_mut(&mut status))
+ .await;
+ status
+ }
+
+ 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 + 32 - 1;
+
+ #[cfg(feature = "defmt")]
+ defmt::trace!("write={} read={}", write_bits, read_bits);
+
+ 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.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
+ .rx()
+ .dma_pull(self.dma.reborrow(), slice::from_mut(&mut status))
+ .await;
+ status
+ }
+}
+
+impl<'d, CS, PIO, const SM: usize, DMA> SpiBusCyw43 for PioSpi<'d, CS, PIO, SM, DMA>
+where
+ CS: Pin,
+ PIO: Instance,
+ DMA: Channel,
+{
+ async fn cmd_write(&mut self, write: &[u32]) -> u32 {
+ self.cs.set_low();
+ let status = self.write(write).await;
+ self.cs.set_high();
+ status
+ }
+
+ async fn cmd_read(&mut self, write: u32, read: &mut [u32]) -> u32 {
+ self.cs.set_low();
+ let status = self.cmd_read(write, read).await;
+ self.cs.set_high();
+ status
+ }
+
+ async fn wait_for_event(&mut self) {
+ self.irq.wait().await;
+ }
+}
diff --git a/cyw43/Cargo.toml b/cyw43/Cargo.toml
new file mode 100644
index 000000000..50fb7c5db
--- /dev/null
+++ b/cyw43/Cargo.toml
@@ -0,0 +1,34 @@
+[package]
+name = "cyw43"
+version = "0.1.0"
+edition = "2021"
+
+[features]
+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.2", 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 }
+log = { version = "0.4.17", optional = true }
+
+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"] }
+
+embedded-hal-1 = { package = "embedded-hal", version = "1.0.0-alpha.11" }
+num_enum = { version = "0.5.7", default-features = false }
+
+[package.metadata.embassy_docs]
+src_base = "https://github.com/embassy-rs/embassy/blob/cyw43-v$VERSION/cyw43/src/"
+src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/cyw43/src/"
+target = "thumbv6m-none-eabi"
+features = ["defmt", "firmware-logs"]
\ No newline at end of file
diff --git a/cyw43/README.md b/cyw43/README.md
new file mode 100644
index 000000000..5b8f3cf40
--- /dev/null
+++ b/cyw43/README.md
@@ -0,0 +1,57 @@
+# cyw43
+
+Rust 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).
+- AP mode (creating an AP)
+- Scanning
+- 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:
+
+- Setting a custom MAC address.
+- Bus sleep (for power consumption optimization)
+
+## Running the examples
+
+- `cargo install probe-rs --features cli`
+- `cd examples/rp`
+### 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 wifi_ap_tcp_server`
+### Example 3: Connect to an existing network and create a server
+- `cargo run --release --bin wifi_tcp_server`
+
+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
+```
+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
+
+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.
+
diff --git a/cyw43/src/bus.rs b/cyw43/src/bus.rs
new file mode 100644
index 000000000..e26f11120
--- /dev/null
+++ b/cyw43/src/bus.rs
@@ -0,0 +1,328 @@
+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.
+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]) -> 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]) -> 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.
+ async fn wait_for_event(&mut self) {
+ yield_now().await;
+ }
+}
+
+pub(crate) struct Bus {
+ backplane_window: u32,
+ pwr: PWR,
+ spi: SPI,
+ status: u32,
+}
+
+impl Bus
+where
+ PWR: OutputPin,
+ SPI: SpiBusCyw43,
+{
+ pub(crate) fn new(pwr: PWR, spi: SPI) -> Self {
+ Self {
+ backplane_window: 0xAAAA_AAAA,
+ pwr,
+ spi,
+ status: 0,
+ }
+ }
+
+ 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)
+ .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;
+ trace!("{:#x}", val);
+ assert_eq!(val, TEST_PATTERN);
+
+ let val = self.read32_swapped(REG_BUS_CTRL).await;
+ 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 | STATUS_ENABLE | INTERRUPT_WITH_STATUS,
+ )
+ .await;
+
+ let val = self.read8(FUNC_BUS, REG_BUS_CTRL).await;
+ trace!("{:#b}", val);
+
+ let val = self.read32(FUNC_BUS, REG_BUS_TEST_RO).await;
+ trace!("{:#x}", val);
+ assert_eq!(val, FEEDBEAD);
+ let val = self.read32(FUNC_BUS, REG_BUS_TEST_RW).await;
+ trace!("{:#x}", val);
+ assert_eq!(val, TEST_PATTERN);
+ }
+
+ 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;
+
+ self.status = self.spi.cmd_read(cmd, &mut buf[..len_in_u32]).await;
+ }
+
+ 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);
+
+ self.status = self.spi.cmd_write(&cmd_buf).await;
+ }
+
+ #[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);
+
+ // 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() {
+ // 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);
+
+ // round `buf` to word boundary, add one extra word for the response delay
+ 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]);
+
+ // 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 + 1];
+
+ 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[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;
+
+ self.status = self.spi.cmd_write(&buf[..(len + 3) / 4 + 1]).await;
+
+ // 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; 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.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 {
+ 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);
+
+ self.status = self.spi.cmd_write(&[cmd, val]).await;
+ }
+
+ 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];
+
+ self.status = self.spi.cmd_read(cmd, &mut buf).await;
+
+ swap16(buf[0])
+ }
+
+ 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)];
+
+ 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 {
+ 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)
+}
diff --git a/cyw43/src/consts.rs b/cyw43/src/consts.rs
new file mode 100644
index 000000000..1f6551589
--- /dev/null
+++ b/cyw43/src/consts.rs
@@ -0,0 +1,318 @@
+#![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;
+pub(crate) const INTERRUPT_HIGH: u32 = 1 << 5;
+pub(crate) const WAKE_UP: u32 = 1 << 7;
+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;
+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_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;
+
+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;
+
+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)]
+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);
+
+#[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)
+ }
+}
diff --git a/cyw43/src/control.rs b/cyw43/src/control.rs
new file mode 100644
index 000000000..c67614dd6
--- /dev/null
+++ b/cyw43/src/control.rs
@@ -0,0 +1,454 @@
+use core::cmp::{max, min};
+
+use ch::driver::LinkState;
+use embassy_net_driver_channel as ch;
+use embassy_time::{Duration, Timer};
+
+pub use crate::bus::SpiBusCyw43;
+use crate::consts::*;
+use crate::events::{Event, EventSubscriber, Events};
+use crate::fmt::Bytes;
+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,
+ ioctl_state: &'a IoctlState,
+}
+
+impl<'a> Control<'a> {
+ pub(crate) fn new(state_ch: ch::StateRunner<'a>, event_sub: &'a Events, ioctl_state: &'a IoctlState) -> Self {
+ Self {
+ state_ch,
+ events: event_sub,
+ ioctl_state,
+ }
+ }
+
+ pub async fn init(&mut self, clm: &[u8]) {
+ const CHUNK_SIZE: usize = 1024;
+
+ debug!("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);
+
+ debug!("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);
+ debug!("mac addr: {:02x}", Bytes(&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);
+
+ debug!("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) -> Result<(), Error> {
+ 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.wait_for_join(i).await
+ }
+
+ 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
+ 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());
+
+ self.wait_for_join(i).await
+ }
+
+ 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;
+
+ // 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 {
+ 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();
+ if status == EStatus::SUCCESS {
+ // successful join
+ self.state_ch.set_link_state(LinkState::Up);
+ debug!("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) {
+ assert!(gpio_n < 3);
+ self.set_iovar_u32x2("gpioout", 1 << gpio_n, if gpio_en { 1 << gpio_n } else { 0 })
+ .await
+ }
+
+ 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;
+
+ // 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, (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;
+
+ // 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());
+ 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]) {
+ self.set_iovar_v::<64>(name, val).await
+ }
+
+ async fn set_iovar_v(&mut self, name: &str, val: &[u8]) {
+ debug!("set {} = {:02x}", name, Bytes(val));
+
+ 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);
+
+ 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 {
+ debug!("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 {
+ 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);
+ let resp_len = ioctl.0.do_ioctl(kind, cmd, iface, buf).await;
+ ioctl.defuse();
+
+ 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;
+
+ 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<'_> {
+ /// 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 {
+ 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/cyw43/src/countries.rs b/cyw43/src/countries.rs
new file mode 100644
index 000000000..fa1e8cace
--- /dev/null
+++ b/cyw43/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/cyw43/src/events.rs b/cyw43/src/events.rs
new file mode 100644
index 000000000..a94c49a0c
--- /dev/null
+++ b/cyw43/src/events.rs
@@ -0,0 +1,400 @@
+#![allow(dead_code)]
+#![allow(non_camel_case_types)]
+
+use core::cell::RefCell;
+
+use embassy_sync::blocking_mutex::raw::NoopRawMutex;
+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))]
+#[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,
+}
+
+// TODO this PubSub can probably be replaced with shared memory to make it a bit more efficient.
+pub type EventQueue = PubSubChannel;
+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 Status {
+ pub event_type: Event,
+ pub status: u32,
+}
+
+#[derive(Clone, Copy)]
+pub enum Payload {
+ None,
+ BssInfo(BssInfo),
+}
+
+#[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,
+ }
+ }
+}
+
+#[derive(Default)]
+struct EventMask {
+ 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 / 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 / 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 / u32::BITS;
+ let bit = n % u32::BITS;
+
+ 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);
+ }
+ }
+
+ #[allow(dead_code)]
+ 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/embassy-cortex-m/src/fmt.rs b/cyw43/src/fmt.rs
similarity index 88%
rename from embassy-cortex-m/src/fmt.rs
rename to cyw43/src/fmt.rs
index f8bb0a035..9534c101c 100644
--- a/embassy-cortex-m/src/fmt.rs
+++ b/cyw43/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.");
@@ -195,9 +197,6 @@ macro_rules! unwrap {
}
}
-#[cfg(feature = "defmt-timestamp-uptime")]
-defmt::timestamp! {"{=u64:us}", crate::time::Instant::now().as_micros() }
-
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub struct NoneError;
@@ -226,3 +225,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/cyw43/src/ioctl.rs b/cyw43/src/ioctl.rs
new file mode 100644
index 000000000..61524c274
--- /dev/null
+++ b/cyw43/src/ioctl.rs
@@ -0,0 +1,126 @@
+use core::cell::{Cell, RefCell};
+use core::future::poll_fn;
+use core::task::{Poll, Waker};
+
+use embassy_sync::waitqueue::WakerRegistration;
+
+use crate::fmt::Bytes;
+
+#[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 },
+}
+
+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,
+}
+
+impl IoctlState {
+ pub fn new() -> Self {
+ Self {
+ state: Cell::new(IoctlStateInner::Done { resp_len: 0 }),
+ wakers: Default::default(),
+ }
+ }
+
+ fn wake_control(&self) {
+ self.wakers.borrow_mut().control.wake();
+ }
+
+ fn register_control(&self, waker: &Waker) {
+ self.wakers.borrow_mut().control.register(waker);
+ }
+
+ fn wake_runner(&self) {
+ self.wakers.borrow_mut().runner.wake();
+ }
+
+ fn register_runner(&self, waker: &Waker) {
+ self.wakers.borrow_mut().runner.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() {
+ Poll::Ready(pending)
+ } else {
+ self.register_runner(cx.waker());
+ Poll::Pending
+ }
+ })
+ .await;
+
+ self.state.set(IoctlStateInner::Sent { buf: pending.buf });
+ 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 {
+ 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() {
+ trace!("IOCTL Response: {:02x}", Bytes(response));
+
+ // TODO fix this
+ (unsafe { &mut *buf }[..response.len()]).copy_from_slice(response);
+
+ self.state.set(IoctlStateInner::Done {
+ resp_len: response.len(),
+ });
+ self.wake_control();
+ } else {
+ warn!("IOCTL Response but no pending Ioctl");
+ }
+ }
+}
diff --git a/cyw43/src/lib.rs b/cyw43/src/lib.rs
new file mode 100644
index 000000000..fd11f3674
--- /dev/null
+++ b/cyw43/src/lib.rs
@@ -0,0 +1,236 @@
+#![no_std]
+#![no_main]
+#![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.
+pub(crate) mod fmt;
+
+mod bus;
+mod consts;
+mod countries;
+mod events;
+mod ioctl;
+mod structs;
+
+mod control;
+mod nvram;
+mod runner;
+
+use core::slice;
+
+use embassy_net_driver_channel as ch;
+use embedded_hal_1::digital::OutputPin;
+use events::Events;
+use ioctl::IoctlState;
+
+use crate::bus::Bus;
+pub use crate::bus::SpiBusCyw43;
+pub use crate::control::{Control, Error as ControlError};
+pub use crate::runner::Runner;
+pub use crate::structs::BssInfo;
+
+const MTU: usize = 1514;
+
+#[allow(unused)]
+#[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,
+ }
+ }
+}
+
+#[allow(unused)]
+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,
+};
+
+pub struct State {
+ ioctl_state: IoctlState,
+ ch: ch::State,
+ events: Events,
+}
+
+impl State {
+ pub fn new() -> Self {
+ Self {
+ ioctl_state: IoctlState::new(),
+ ch: ch::State::new(),
+ events: Events::new(),
+ }
+ }
+}
+
+#[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,
+ PowerManagementMode::None => 0,
+ _ => 2,
+ }
+ }
+}
+
+pub type NetDriver<'a> = ch::Device<'a, MTU>;
+
+pub async fn new<'a, PWR, SPI>(
+ state: &'a mut State,
+ pwr: PWR,
+ spi: SPI,
+ firmware: &[u8],
+) -> (NetDriver<'a>, Control<'a>, Runner<'a, PWR, SPI>)
+where
+ PWR: OutputPin,
+ SPI: SpiBusCyw43,
+{
+ let (ch_runner, device) = ch::new(&mut state.ch, [0; 6]);
+ let state_ch = ch_runner.state_runner();
+
+ let mut runner = Runner::new(ch_runner, Bus::new(pwr, spi), &state.ioctl_state, &state.events);
+
+ runner.init(firmware).await;
+
+ (
+ device,
+ Control::new(state_ch, &state.events, &state.ioctl_state),
+ 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/cyw43/src/nvram.rs b/cyw43/src/nvram.rs
new file mode 100644
index 000000000..964a3128d
--- /dev/null
+++ b/cyw43/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/cyw43/src/runner.rs b/cyw43/src/runner.rs
new file mode 100644
index 000000000..1c187faa5
--- /dev/null
+++ b/cyw43/src/runner.rs
@@ -0,0 +1,585 @@
+use embassy_futures::select::{select3, Either3};
+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::{Event, Events, Status};
+use crate::fmt::Bytes;
+use crate::ioctl::{IoctlState, IoctlType, PendingIoctl};
+use crate::nvram::NVRAM;
+use crate::structs::*;
+use crate::{events, slice8_mut, Core, CHIP, MTU};
+
+#[cfg(feature = "firmware-logs")]
+struct LogState {
+ addr: u32,
+ last_idx: usize,
+ buf: [u8; 256],
+ buf_count: usize,
+}
+
+#[cfg(feature = "firmware-logs")]
+impl Default for LogState {
+ fn default() -> Self {
+ Self {
+ addr: Default::default(),
+ last_idx: Default::default(),
+ buf: [0; 256],
+ buf_count: Default::default(),
+ }
+ }
+}
+
+pub struct Runner<'a, PWR, SPI> {
+ ch: ch::Runner<'a, MTU>,
+ bus: Bus,
+
+ ioctl_state: &'a IoctlState,
+ ioctl_id: u16,
+ sdpcm_seq: u8,
+ sdpcm_seq_max: u8,
+
+ events: &'a Events,
+
+ #[cfg(feature = "firmware-logs")]
+ log: LogState,
+}
+
+impl<'a, PWR, SPI> Runner<'a, PWR, SPI>
+where
+ PWR: OutputPin,
+ SPI: SpiBusCyw43,
+{
+ pub(crate) fn new(
+ ch: ch::Runner<'a, MTU>,
+ bus: Bus,
+ ioctl_state: &'a IoctlState,
+ events: &'a Events,
+ ) -> Self {
+ Self {
+ ch,
+ bus,
+ ioctl_state,
+ ioctl_id: 0,
+ sdpcm_seq: 0,
+ sdpcm_seq_max: 1,
+ events,
+ #[cfg(feature = "firmware-logs")]
+ log: LogState::default(),
+ }
+ }
+
+ pub(crate) async fn init(&mut self, firmware: &[u8]) {
+ self.bus.init().await;
+
+ // Init ALP (Active Low Power) clock
+ self.bus
+ .write8(FUNC_BACKPLANE, REG_BACKPLANE_CHIP_CLOCK_CSR, BACKPLANE_ALP_AVAIL_REQ)
+ .await;
+ debug!("waiting for clock...");
+ while self.bus.read8(FUNC_BACKPLANE, REG_BACKPLANE_CHIP_CLOCK_CSR).await & BACKPLANE_ALP_AVAIL == 0 {}
+ debug!("clock ok");
+
+ let chip_id = self.bus.bp_read16(0x1800_0000).await;
+ debug!("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;
+
+ debug!("loading fw");
+ self.bus.bp_write(ram_addr, firmware).await;
+
+ debug!("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!
+ debug!("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;
+
+ 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...
+ self.bus
+ .write8(FUNC_BACKPLANE, REG_BACKPLANE_FUNCTION2_WATERMARK, 32)
+ .await;
+
+ // wait for wifi startup
+ 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.
+ // 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;
+ //debug!("waiting for HT clock...");
+ //while self.bus.read8(FUNC_BACKPLANE, REG_BACKPLANE_CHIP_CLOCK_CSR).await & 0x80 == 0 {}
+ //debug!("clock ok");
+
+ #[cfg(feature = "firmware-logs")]
+ self.log_init().await;
+
+ debug!("wifi init done");
+ }
+
+ #[cfg(feature = "firmware-logs")]
+ async fn log_init(&mut self) {
+ // Initialize shared memory for logging.
+
+ let addr = CHIP.atcm_ram_base_address + CHIP.chip_ram_size - 4 - CHIP.socram_srmem_size;
+ let shared_addr = self.bus.bp_read32(addr).await;
+ debug!("shared_addr {:08x}", shared_addr);
+
+ let mut shared = [0; SharedMemData::SIZE];
+ self.bus.bp_read(shared_addr, &mut shared).await;
+ let shared = SharedMemData::from_bytes(&shared);
+
+ self.log.addr = shared.console_addr + 8;
+ }
+
+ #[cfg(feature = "firmware-logs")]
+ async fn log_read(&mut self) {
+ // Read log struct
+ let mut log = [0; SharedMemLog::SIZE];
+ self.bus.bp_read(self.log.addr, &mut log).await;
+ let log = SharedMemLog::from_bytes(&log);
+
+ let idx = log.idx as usize;
+
+ // If pointer hasn't moved, no need to do anything.
+ if idx == self.log.last_idx {
+ return;
+ }
+
+ // Read entire buf for now. We could read only what we need, but then we
+ // run into annoying alignment issues in `bp_read`.
+ let mut buf = [0; 0x400];
+ self.bus.bp_read(log.buf, &mut buf).await;
+
+ while self.log.last_idx != idx as usize {
+ let b = buf[self.log.last_idx];
+ if b == b'\r' || b == b'\n' {
+ if self.log.buf_count != 0 {
+ let s = unsafe { core::str::from_utf8_unchecked(&self.log.buf[..self.log.buf_count]) };
+ debug!("LOGS: {}", s);
+ self.log.buf_count = 0;
+ }
+ } else if self.log.buf_count < self.log.buf.len() {
+ self.log.buf[self.log.buf_count] = b;
+ self.log.buf_count += 1;
+ }
+
+ self.log.last_idx += 1;
+ if self.log.last_idx == 0x400 {
+ self.log.last_idx = 0;
+ }
+ }
+ }
+
+ pub async fn run(mut self) -> ! {
+ let mut buf = [0; 512];
+ loop {
+ #[cfg(feature = "firmware-logs")]
+ self.log_read().await;
+
+ if self.has_credit() {
+ let ioctl = self.ioctl_state.wait_pending();
+ let tx = self.ch.tx_buf();
+ let ev = self.bus.wait_for_event();
+
+ match select3(ioctl, tx, ev).await {
+ Either3::First(PendingIoctl {
+ buf: iobuf,
+ kind,
+ cmd,
+ iface,
+ }) => {
+ self.send_ioctl(kind, cmd, iface, unsafe { &*iobuf }).await;
+ self.check_status(&mut buf).await;
+ }
+ Either3::Second(packet) => {
+ trace!("tx pkt {:02x}", Bytes(&packet[..packet.len().min(48)]));
+
+ let mut buf = [0; 512];
+ let buf8 = slice8_mut(&mut buf);
+
+ // There MUST be 2 bytes of padding between the SDPCM and BDC headers.
+ // And ONLY for data packets!
+ // No idea why, but the firmware will append two zero bytes to the tx'd packets
+ // otherwise. If the packet is exactly 1514 bytes (the max MTU), this makes it
+ // be oversized and get dropped.
+ // WHD adds it here https://github.com/Infineon/wifi-host-driver/blob/c04fcbb6b0d049304f376cf483fd7b1b570c8cd5/WiFi_Host_Driver/src/include/whd_sdpcm.h#L90
+ // and adds it to the header size her https://github.com/Infineon/wifi-host-driver/blob/c04fcbb6b0d049304f376cf483fd7b1b570c8cd5/WiFi_Host_Driver/src/whd_sdpcm.c#L597
+ // ¯\_(ツ)_/¯
+ const PADDING_SIZE: usize = 2;
+ let total_len = SdpcmHeader::SIZE + PADDING_SIZE + BdcHeader::SIZE + packet.len();
+
+ let seq = self.sdpcm_seq;
+ self.sdpcm_seq = self.sdpcm_seq.wrapping_add(1);
+
+ let sdpcm_header = SdpcmHeader {
+ len: total_len as u16, // TODO does this len need to be rounded up to u32?
+ len_inv: !total_len as u16,
+ sequence: seq,
+ channel_and_flags: CHANNEL_TYPE_DATA,
+ next_length: 0,
+ header_length: (SdpcmHeader::SIZE + PADDING_SIZE) as _,
+ wireless_flow_control: 0,
+ bus_data_credit: 0,
+ reserved: [0, 0],
+ };
+
+ let bdc_header = BdcHeader {
+ flags: BDC_VERSION << BDC_VERSION_SHIFT,
+ priority: 0,
+ flags2: 0,
+ data_offset: 0,
+ };
+ trace!("tx {:?}", sdpcm_header);
+ trace!(" {:?}", bdc_header);
+
+ buf8[0..SdpcmHeader::SIZE].copy_from_slice(&sdpcm_header.to_bytes());
+ buf8[SdpcmHeader::SIZE + PADDING_SIZE..][..BdcHeader::SIZE]
+ .copy_from_slice(&bdc_header.to_bytes());
+ buf8[SdpcmHeader::SIZE + PADDING_SIZE + BdcHeader::SIZE..][..packet.len()]
+ .copy_from_slice(packet);
+
+ let total_len = (total_len + 3) & !3; // round up to 4byte
+
+ trace!(" {:02x}", Bytes(&buf8[..total_len.min(48)]));
+
+ self.bus.wlan_write(&buf[..(total_len / 4)]).await;
+ self.ch.tx_done();
+ self.check_status(&mut buf).await;
+ }
+ Either3::Third(()) => {
+ self.handle_irq(&mut buf).await;
+ }
+ }
+ } else {
+ warn!("TX stalled");
+ self.bus.wait_for_event().await;
+ self.handle_irq(&mut buf).await;
+ }
+ }
+ }
+
+ /// Wait for IRQ on F2 packet available
+ async fn handle_irq(&mut self, buf: &mut [u32; 512]) {
+ // Receive stuff
+ let irq = self.bus.read16(FUNC_BUS, REG_BUS_INTERRUPT).await;
+ trace!("irq{}", FormatInterrupt(irq));
+
+ if irq & IRQ_F2_PACKET_AVAILABLE != 0 {
+ self.check_status(buf).await;
+ }
+
+ if irq & IRQ_DATA_UNAVAILABLE != 0 {
+ // TODO what should we do here?
+ warn!("IRQ DATA_UNAVAILABLE, clearing...");
+ self.bus.write16(FUNC_BUS, REG_BUS_INTERRUPT, 1).await;
+ }
+ }
+
+ /// Handle F2 events while status register is set
+ async fn check_status(&mut self, buf: &mut [u32; 512]) {
+ loop {
+ let status = self.bus.status();
+ trace!("check status{}", FormatStatus(status));
+
+ if status & STATUS_F2_PKT_AVAILABLE != 0 {
+ let len = (status & STATUS_F2_PKT_LEN_MASK) >> STATUS_F2_PKT_LEN_SHIFT;
+ self.bus.wlan_read(buf, len).await;
+ trace!("rx {:02x}", Bytes(&slice8_mut(buf)[..(len as usize).min(48)]));
+ self.rx(&mut slice8_mut(buf)[..len as usize]);
+ } else {
+ break;
+ }
+ }
+ }
+
+ fn rx(&mut self, packet: &mut [u8]) {
+ let Some((sdpcm_header, payload)) = SdpcmHeader::parse(packet) else {
+ return;
+ };
+
+ self.update_credit(&sdpcm_header);
+
+ let channel = sdpcm_header.channel_and_flags & 0x0f;
+
+ match channel {
+ CHANNEL_TYPE_CONTROL => {
+ let Some((cdc_header, response)) = CdcHeader::parse(payload) else {
+ return;
+ };
+ trace!(" {:?}", cdc_header);
+
+ if cdc_header.id == self.ioctl_id {
+ if cdc_header.status != 0 {
+ // TODO: propagate error instead
+ panic!("IOCTL error {}", cdc_header.status as i32);
+ }
+
+ self.ioctl_state.ioctl_done(response);
+ }
+ }
+ CHANNEL_TYPE_EVENT => {
+ let Some((_, bdc_packet)) = BdcHeader::parse(payload) else {
+ warn!("BDC event, incomplete header");
+ return;
+ };
+
+ let Some((event_packet, evt_data)) = EventPacket::parse(bdc_packet) else {
+ warn!("BDC event, incomplete data");
+ return;
+ };
+
+ const ETH_P_LINK_CTL: u16 = 0x886c; // HPNA, wlan link local tunnel, according to linux if_ether.h
+ if event_packet.eth.ether_type != ETH_P_LINK_CTL {
+ warn!(
+ "unexpected ethernet type 0x{:04x}, expected Broadcom ether type 0x{:04x}",
+ event_packet.eth.ether_type, ETH_P_LINK_CTL
+ );
+ return;
+ }
+ const BROADCOM_OUI: &[u8] = &[0x00, 0x10, 0x18];
+ if event_packet.hdr.oui != BROADCOM_OUI {
+ warn!(
+ "unexpected ethernet OUI {:02x}, expected Broadcom OUI {:02x}",
+ Bytes(&event_packet.hdr.oui),
+ Bytes(BROADCOM_OUI)
+ );
+ return;
+ }
+ const BCMILCP_SUBTYPE_VENDOR_LONG: u16 = 32769;
+ if event_packet.hdr.subtype != BCMILCP_SUBTYPE_VENDOR_LONG {
+ warn!("unexpected subtype {}", event_packet.hdr.subtype);
+ return;
+ }
+
+ const BCMILCP_BCM_SUBTYPE_EVENT: u16 = 1;
+ if event_packet.hdr.user_subtype != BCMILCP_BCM_SUBTYPE_EVENT {
+ warn!("unexpected user_subtype {}", event_packet.hdr.subtype);
+ return;
+ }
+
+ let evt_type = events::Event::from(event_packet.msg.event_type as u8);
+ debug!(
+ "=== EVENT {:?}: {:?} {:02x}",
+ evt_type,
+ event_packet.msg,
+ Bytes(evt_data)
+ );
+
+ if self.events.mask.is_enabled(evt_type) {
+ let status = event_packet.msg.status;
+ let event_payload = match evt_type {
+ Event::ESCAN_RESULT if status == EStatus::PARTIAL => {
+ let Some((_, bss_info)) = ScanResults::parse(evt_data) else {
+ return;
+ };
+ let Some(bss_info) = BssInfo::parse(bss_info) else {
+ return;
+ };
+ events::Payload::BssInfo(*bss_info)
+ }
+ Event::ESCAN_RESULT => events::Payload::None,
+ _ => events::Payload::None,
+ };
+
+ // this intentionally uses the non-blocking publish immediate
+ // publish() is a deadlock risk in the current design as awaiting here prevents ioctls
+ // The `Runner` always yields when accessing the device, so consumers always have a chance to receive the event
+ // (if they are actively awaiting the queue)
+ self.events.queue.publish_immediate(events::Message::new(
+ Status {
+ event_type: evt_type,
+ status,
+ },
+ event_payload,
+ ));
+ }
+ }
+ CHANNEL_TYPE_DATA => {
+ let Some((_, packet)) = BdcHeader::parse(payload) else {
+ return;
+ };
+ trace!("rx pkt {:02x}", Bytes(&packet[..packet.len().min(48)]));
+
+ match self.ch.try_rx_buf() {
+ Some(buf) => {
+ buf[..packet.len()].copy_from_slice(packet);
+ self.ch.rx_done(packet.len())
+ }
+ None => warn!("failed to push rxd packet to the channel."),
+ }
+ }
+ _ => {}
+ }
+ }
+
+ fn update_credit(&mut self, sdpcm_header: &SdpcmHeader) {
+ if sdpcm_header.channel_and_flags & 0xf < 3 {
+ let mut sdpcm_seq_max = sdpcm_header.bus_data_credit;
+ if sdpcm_seq_max.wrapping_sub(self.sdpcm_seq) > 0x40 {
+ sdpcm_seq_max = self.sdpcm_seq + 2;
+ }
+ self.sdpcm_seq_max = sdpcm_seq_max;
+ }
+ }
+
+ fn has_credit(&self) -> bool {
+ self.sdpcm_seq != self.sdpcm_seq_max && self.sdpcm_seq_max.wrapping_sub(self.sdpcm_seq) & 0x80 == 0
+ }
+
+ async fn send_ioctl(&mut self, kind: IoctlType, cmd: u32, iface: u32, data: &[u8]) {
+ let mut buf = [0; 512];
+ let buf8 = slice8_mut(&mut buf);
+
+ let total_len = SdpcmHeader::SIZE + CdcHeader::SIZE + data.len();
+
+ let sdpcm_seq = self.sdpcm_seq;
+ self.sdpcm_seq = self.sdpcm_seq.wrapping_add(1);
+ self.ioctl_id = self.ioctl_id.wrapping_add(1);
+
+ let sdpcm_header = SdpcmHeader {
+ len: total_len as u16, // TODO does this len need to be rounded up to u32?
+ len_inv: !total_len as u16,
+ sequence: sdpcm_seq,
+ channel_and_flags: CHANNEL_TYPE_CONTROL,
+ next_length: 0,
+ header_length: SdpcmHeader::SIZE as _,
+ wireless_flow_control: 0,
+ bus_data_credit: 0,
+ reserved: [0, 0],
+ };
+
+ let cdc_header = CdcHeader {
+ cmd: cmd,
+ len: data.len() as _,
+ flags: kind as u16 | (iface as u16) << 12,
+ id: self.ioctl_id,
+ status: 0,
+ };
+ trace!("tx {:?}", sdpcm_header);
+ trace!(" {:?}", cdc_header);
+
+ buf8[0..SdpcmHeader::SIZE].copy_from_slice(&sdpcm_header.to_bytes());
+ buf8[SdpcmHeader::SIZE..][..CdcHeader::SIZE].copy_from_slice(&cdc_header.to_bytes());
+ buf8[SdpcmHeader::SIZE + CdcHeader::SIZE..][..data.len()].copy_from_slice(data);
+
+ let total_len = (total_len + 3) & !3; // round up to 4byte
+
+ trace!(" {:02x}", Bytes(&buf8[..total_len.min(48)]));
+
+ self.bus.wlan_write(&buf[..total_len / 4]).await;
+ }
+
+ async fn core_disable(&mut self, core: Core) {
+ let base = core.base_addr();
+
+ // Dummy read?
+ let _ = self.bus.bp_read8(base + AI_RESETCTRL_OFFSET).await;
+
+ // Check it isn't already reset
+ let r = self.bus.bp_read8(base + AI_RESETCTRL_OFFSET).await;
+ if r & AI_RESETCTRL_BIT_RESET != 0 {
+ return;
+ }
+
+ self.bus.bp_write8(base + AI_IOCTRL_OFFSET, 0).await;
+ let _ = self.bus.bp_read8(base + AI_IOCTRL_OFFSET).await;
+
+ block_for(Duration::from_millis(1));
+
+ self.bus
+ .bp_write8(base + AI_RESETCTRL_OFFSET, AI_RESETCTRL_BIT_RESET)
+ .await;
+ let _ = self.bus.bp_read8(base + AI_RESETCTRL_OFFSET).await;
+ }
+
+ async fn core_reset(&mut self, core: Core) {
+ self.core_disable(core).await;
+
+ let base = core.base_addr();
+ self.bus
+ .bp_write8(base + AI_IOCTRL_OFFSET, AI_IOCTRL_BIT_FGC | AI_IOCTRL_BIT_CLOCK_EN)
+ .await;
+ let _ = self.bus.bp_read8(base + AI_IOCTRL_OFFSET).await;
+
+ self.bus.bp_write8(base + AI_RESETCTRL_OFFSET, 0).await;
+
+ Timer::after(Duration::from_millis(1)).await;
+
+ self.bus
+ .bp_write8(base + AI_IOCTRL_OFFSET, AI_IOCTRL_BIT_CLOCK_EN)
+ .await;
+ let _ = self.bus.bp_read8(base + AI_IOCTRL_OFFSET).await;
+
+ Timer::after(Duration::from_millis(1)).await;
+ }
+
+ async fn core_is_up(&mut self, core: Core) -> bool {
+ let base = core.base_addr();
+
+ let io = self.bus.bp_read8(base + AI_IOCTRL_OFFSET).await;
+ if io & (AI_IOCTRL_BIT_FGC | AI_IOCTRL_BIT_CLOCK_EN) != AI_IOCTRL_BIT_CLOCK_EN {
+ debug!("core_is_up: returning false due to bad ioctrl {:02x}", io);
+ return false;
+ }
+
+ let r = self.bus.bp_read8(base + AI_RESETCTRL_OFFSET).await;
+ if r & (AI_RESETCTRL_BIT_RESET) != 0 {
+ debug!("core_is_up: returning false due to bad resetctrl {:02x}", r);
+ return false;
+ }
+
+ true
+ }
+}
diff --git a/cyw43/src/structs.rs b/cyw43/src/structs.rs
new file mode 100644
index 000000000..5ba633c74
--- /dev/null
+++ b/cyw43/src/structs.rs
@@ -0,0 +1,496 @@
+use crate::events::Event;
+use crate::fmt::Bytes;
+
+macro_rules! impl_bytes {
+ ($t:ident) => {
+ impl $t {
+ pub const SIZE: usize = core::mem::size_of::();
+
+ #[allow(unused)]
+ pub fn to_bytes(&self) -> [u8; Self::SIZE] {
+ unsafe { core::mem::transmute(*self) }
+ }
+
+ #[allow(unused)]
+ pub fn from_bytes(bytes: &[u8; Self::SIZE]) -> &Self {
+ let alignment = core::mem::align_of::();
+ assert_eq!(
+ bytes.as_ptr().align_offset(alignment),
+ 0,
+ "{} is not aligned",
+ core::any::type_name::()
+ );
+ unsafe { core::mem::transmute(bytes) }
+ }
+
+ #[allow(unused)]
+ pub fn from_bytes_mut(bytes: &mut [u8; Self::SIZE]) -> &mut Self {
+ let alignment = core::mem::align_of::();
+ assert_eq!(
+ bytes.as_ptr().align_offset(alignment),
+ 0,
+ "{} is not aligned",
+ core::any::type_name::()
+ );
+
+ unsafe { core::mem::transmute(bytes) }
+ }
+ }
+ };
+}
+
+#[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(Debug, Clone, Copy)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+#[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],
+}
+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)]
+#[repr(C, packed(2))]
+pub struct CdcHeader {
+ pub cmd: u32,
+ pub len: u32,
+ pub flags: u16,
+ pub id: u16,
+ pub status: u32,
+}
+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 {
+ 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;
+
+#[derive(Debug, Clone, Copy)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+#[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,
+}
+impl_bytes!(BdcHeader);
+
+impl BdcHeader {
+ pub fn parse(packet: &mut [u8]) -> Option<(&mut Self, &mut [u8])> {
+ if packet.len() < Self::SIZE {
+ return None;
+ }
+
+ 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 * bdc_header.data_offset as usize;
+
+ let bdc_packet = bdc_packet.get_mut(packet_start..)?;
+ trace!(" {:02x}", Bytes(&bdc_packet[..bdc_packet.len().min(36)]));
+
+ Some((bdc_header, bdc_packet))
+ }
+}
+
+#[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(Debug, Clone, Copy)]
+// #[cfg_attr(feature = "defmt", derive(defmt::Format))]
+#[repr(C, packed(2))]
+pub struct EventMessage {
+ /// 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!(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();
+ 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)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+#[repr(C, packed(2))]
+pub struct EventPacket {
+ pub eth: EthernetHeader,
+ pub hdr: EventHeader,
+ pub msg: EventMessage,
+}
+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();
+ self.msg.byteswap();
+ }
+}
+
+#[derive(Clone, Copy)]
+#[repr(C)]
+pub struct DownloadHeader {
+ pub flag: u16, //
+ pub dload_type: u16,
+ pub len: u32,
+ pub crc: u32,
+}
+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;
+pub const DOWNLOAD_FLAG_HANDLER_VER: u16 = 0x1000;
+
+// Country Locale Matrix (CLM)
+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],
+ pub rev: i32,
+ 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 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)]
+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)]
+pub struct EventMask {
+ pub iface: u32,
+ 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));
+ }
+}
+
+/// 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(),
+ ))
+ }
+}
diff --git a/docs/modules/ROOT/examples/basic/Cargo.toml b/docs/modules/ROOT/examples/basic/Cargo.toml
index ae124a871..237ae0ac2 100644
--- a/docs/modules/ROOT/examples/basic/Cargo.toml
+++ b/docs/modules/ROOT/examples/basic/Cargo.toml
@@ -3,16 +3,16 @@ authors = ["Dario Nieuwenhuis "]
edition = "2018"
name = "embassy-basic-example"
version = "0.1.0"
+license = "MIT OR Apache-2.0"
[dependencies]
-embassy-executor = { version = "0.1.0", path = "../../../../../embassy-executor", features = ["defmt", "nightly"] }
+embassy-executor = { version = "0.2.0", path = "../../../../../embassy-executor", features = ["defmt", "nightly", "integrated-timers", "arch-cortex-m", "executor-thread"] }
embassy-time = { version = "0.1.0", path = "../../../../../embassy-time", features = ["defmt", "nightly"] }
embassy-nrf = { version = "0.1.0", path = "../../../../../embassy-nrf", features = ["defmt", "nrf52840", "time-driver-rtc1", "gpiote", "nightly"] }
defmt = "0.3"
defmt-rtt = "0.3"
-cortex-m = "0.7.3"
+cortex-m = { version = "0.7.6", features = ["critical-section-single-core"] }
cortex-m-rt = "0.7.0"
-embedded-hal = "0.2.6"
panic-probe = { version = "0.3", features = ["print-defmt"] }
diff --git a/examples/nrf/build.rs b/docs/modules/ROOT/examples/basic/build.rs
similarity index 100%
rename from examples/nrf/build.rs
rename to docs/modules/ROOT/examples/basic/build.rs
diff --git a/examples/nrf/memory.x b/docs/modules/ROOT/examples/basic/memory.x
similarity index 100%
rename from examples/nrf/memory.x
rename to docs/modules/ROOT/examples/basic/memory.x
diff --git a/docs/modules/ROOT/examples/layer-by-layer/Cargo.toml b/docs/modules/ROOT/examples/layer-by-layer/Cargo.toml
index 9048d9302..943249a17 100644
--- a/docs/modules/ROOT/examples/layer-by-layer/Cargo.toml
+++ b/docs/modules/ROOT/examples/layer-by-layer/Cargo.toml
@@ -10,7 +10,6 @@ members = [
[patch.crates-io]
embassy-executor = { path = "../../../../../embassy-executor" }
embassy-stm32 = { path = "../../../../../embassy-stm32" }
-stm32-metapac = { path = "../../../../../stm32-metapac" }
[profile.release]
codegen-units = 1
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 e2933076f..a7236ed5e 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
@@ -2,12 +2,13 @@
name = "blinky-async"
version = "0.1.0"
edition = "2021"
+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.1.0", default-features = false, features = ["nightly"] }
+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 dbd3aba8b..c15de2db2 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
@@ -2,11 +2,12 @@
name = "blinky-hal"
version = "0.1.0"
edition = "2021"
+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"
diff --git a/docs/modules/ROOT/examples/layer-by-layer/blinky-irq/Cargo.toml b/docs/modules/ROOT/examples/layer-by-layer/blinky-irq/Cargo.toml
index 0dd326015..9733658b6 100644
--- a/docs/modules/ROOT/examples/layer-by-layer/blinky-irq/Cargo.toml
+++ b/docs/modules/ROOT/examples/layer-by-layer/blinky-irq/Cargo.toml
@@ -2,6 +2,7 @@
name = "blinky-irq"
version = "0.1.0"
edition = "2021"
+license = "MIT OR Apache-2.0"
[dependencies]
cortex-m = "0.7"
diff --git a/docs/modules/ROOT/examples/layer-by-layer/blinky-irq/src/main.rs b/docs/modules/ROOT/examples/layer-by-layer/blinky-irq/src/main.rs
index 743d0c342..aecba0755 100644
--- a/docs/modules/ROOT/examples/layer-by-layer/blinky-irq/src/main.rs
+++ b/docs/modules/ROOT/examples/layer-by-layer/blinky-irq/src/main.rs
@@ -20,13 +20,13 @@ fn main() -> ! {
let led = Output::new(p.PB14, Level::Low, Speed::Low);
let mut button = Input::new(p.PC13, Pull::Up);
- cortex_m::interrupt::free(|cs| unsafe {
+ cortex_m::interrupt::free(|cs| {
enable_interrupt(&mut button);
LED.borrow(cs).borrow_mut().replace(led);
BUTTON.borrow(cs).borrow_mut().replace(button);
- NVIC::unmask(pac::Interrupt::EXTI15_10);
+ unsafe { NVIC::unmask(pac::Interrupt::EXTI15_10) };
});
loop {
@@ -64,25 +64,21 @@ const PORT: u8 = 2;
const PIN: usize = 13;
fn check_interrupt(_pin: &mut Input<'static, P>) -> bool {
let exti = pac::EXTI;
- unsafe {
- let pin = PIN;
- let lines = exti.pr(0).read();
- lines.line(pin)
- }
+ let pin = PIN;
+ let lines = exti.pr(0).read();
+ lines.line(pin)
}
fn clear_interrupt(_pin: &mut Input<'static, P>) {
let exti = pac::EXTI;
- unsafe {
- let pin = PIN;
- let mut lines = exti.pr(0).read();
- lines.set_line(pin, true);
- exti.pr(0).write_value(lines);
- }
+ let pin = PIN;
+ let mut lines = exti.pr(0).read();
+ lines.set_line(pin, true);
+ exti.pr(0).write_value(lines);
}
fn enable_interrupt(_pin: &mut Input<'static, P>) {
- cortex_m::interrupt::free(|_| unsafe {
+ cortex_m::interrupt::free(|_| {
let rcc = pac::RCC;
rcc.apb2enr().modify(|w| w.set_syscfgen(true));
diff --git a/docs/modules/ROOT/examples/layer-by-layer/blinky-pac/Cargo.toml b/docs/modules/ROOT/examples/layer-by-layer/blinky-pac/Cargo.toml
index e7f4f5d1f..f872b94cb 100644
--- a/docs/modules/ROOT/examples/layer-by-layer/blinky-pac/Cargo.toml
+++ b/docs/modules/ROOT/examples/layer-by-layer/blinky-pac/Cargo.toml
@@ -2,11 +2,12 @@
name = "blinky-pac"
version = "0.1.0"
edition = "2021"
+license = "MIT OR Apache-2.0"
[dependencies]
cortex-m = "0.7"
cortex-m-rt = "0.7"
-stm32-metapac = { version = "0.1.0", features = ["stm32l475vg", "memory-x"] }
+stm32-metapac = { version = "1", features = ["stm32l475vg", "memory-x"] }
defmt = "0.3.0"
defmt-rtt = "0.3.0"
diff --git a/docs/modules/ROOT/pages/basic_application.adoc b/docs/modules/ROOT/pages/basic_application.adoc
index 4dc4a6359..3f4f16e28 100644
--- a/docs/modules/ROOT/pages/basic_application.adoc
+++ b/docs/modules/ROOT/pages/basic_application.adoc
@@ -21,7 +21,7 @@ Then, what follows are some declarations on how to deal with panics and faults.
[source,rust]
----
-include::example$basic/src/main.rs[lines="11..12"]
+include::example$basic/src/main.rs[lines="10"]
----
=== Task declaration
@@ -30,7 +30,7 @@ After a bit of import declaration, the tasks run by the application should be de
[source,rust]
----
-include::example$basic/src/main.rs[lines="13..22"]
+include::example$basic/src/main.rs[lines="12..20"]
----
An embassy task must be declared `async`, and may NOT take generic arguments. In this case, we are handed the LED that should be blinked and the interval of the blinking.
@@ -45,23 +45,10 @@ The `Spawner` is the way the main application spawns other tasks. The `Periphera
[source,rust]
----
-include::example$basic/src/main.rs[lines="23..-1"]
+include::example$basic/src/main.rs[lines="22..-1"]
----
-`#[embassy_executor::main]` takes an optional `config` parameter specifying a function that returns an instance of HAL's `Config` struct. For example:
-
-```rust
-fn embassy_config() -> embassy_nrf::config::Config {
- embassy_nrf::config::Config::default()
-}
-
-#[embassy_executor::main(config = "embassy_config()")]
-async fn main(_spawner: Spawner, p: embassy_nrf::Peripherals) {
- // ...
-}
-```
-
-What happens when the `blinker` task have been spawned and main returns? Well, the main entry point is actually just like any other task, except that you can only have one and it takes some specific type arguments. The magic lies within the `#[embassy::main]` macro. The macro does the following:
+What happens when the `blinker` task has been spawned and main returns? Well, the main entry point is actually just like any other task, except that you can only have one and it takes some specific type arguments. The magic lies within the `#[embassy::main]` macro. The macro does the following:
. Creates an Embassy Executor
. Initializes the microcontroller HAL to get the `Peripherals`
@@ -76,7 +63,7 @@ The project definition needs to contain the embassy dependencies:
[source,toml]
----
-include::example$basic/Cargo.toml[lines="8..9"]
+include::example$basic/Cargo.toml[lines="9..11"]
----
Depending on your microcontroller, you may need to replace `embassy-nrf` with something else (`embassy-stm32` for STM32. Remember to update feature flags as well).
diff --git a/docs/modules/ROOT/pages/bootloader.adoc b/docs/modules/ROOT/pages/bootloader.adoc
index ae92e9d5d..b7215e52a 100644
--- a/docs/modules/ROOT/pages/bootloader.adoc
+++ b/docs/modules/ROOT/pages/bootloader.adoc
@@ -6,7 +6,7 @@ The bootloader can be used either as a library or be flashed directly if you are
By design, the bootloader does not provide any network capabilities. Networking capabilities for fetching new firmware can be provided by the user application, using the bootloader as a library for updating the firmware, or by using the bootloader as a library and adding this capability yourself.
-The bootloader supports both internal and external flash by relying on the `embedded-storage` traits.
+The bootloader supports both internal and external flash by relying on the `embedded-storage` traits. The bootloader optionally supports the verification of firmware that has been digitally signed (recommended).
== Hardware support
@@ -15,6 +15,7 @@ The bootloader supports
* nRF52 with and without softdevice
* STM32 L4, WB, WL, L1, L0, F3, F7 and H7
+* Raspberry Pi: RP2040
In general, the bootloader works on any platform that implements the `embedded-storage` traits for its internal flash, but may require custom initialization code to work.
@@ -25,12 +26,69 @@ image::bootloader_flash.png[Bootloader flash layout]
The bootloader divides the storage into 4 main partitions, configurable when creating the bootloader
instance or via linker scripts:
-* BOOTLOADER - Where the bootloader is placed. The bootloader itself consumes about 8kB of flash.
-* ACTIVE - Where the main application is placed. The bootloader will attempt to load the application at the start of this partition. This partition is only written to by the bootloader.
-* DFU - Where the application-to-be-swapped is placed. This partition is written to by the application.
-* BOOTLOADER STATE - Where the bootloader stores the current state describing if the active and dfu partitions need to be swapped. When the new firmware has been written to the DFU partition, a flag is set to instruct the bootloader that the partitions should be swapped.
+* BOOTLOADER - Where the bootloader is placed. The bootloader itself consumes about 8kB of flash, but if you need to debug it and have space available, increasing this to 24kB will allow you to run the bootloader with probe-rs.
+* ACTIVE - Where the main application is placed. The bootloader will attempt to load the application at the start of this partition. This partition is only written to by the bootloader. The size required for this partition depends on the size of your application.
+* DFU - Where the application-to-be-swapped is placed. This partition is written to by the application. This partition must be at least 1 page bigger than the ACTIVE partition, since the swap algorithm uses the extra space to ensure power safe copy of data:
++
+Partition Size~dfu~= Partition Size~active~+ Page Size~active~
++
+All values are specified in bytes.
+
+* BOOTLOADER STATE - Where the bootloader stores the current state describing if the active and dfu partitions need to be swapped. When the new firmware has been written to the DFU partition, a magic field is written to instruct the bootloader that the partitions should be swapped. This partition must be able to store a magic field as well as the partition swap progress. The partition size given by:
++
+Partition Size~state~ = Write Size~state~ + (2 × Partition Size~active~ / Page Size~active~)
++
+All values are specified in bytes.
The partitions for ACTIVE (+BOOTLOADER), DFU and BOOTLOADER_STATE may be placed in separate flash. The page size used by the bootloader is determined by the lowest common multiple of the ACTIVE and DFU page sizes.
The BOOTLOADER_STATE partition must be big enough to store one word per page in the ACTIVE and DFU partitions combined.
The bootloader has a platform-agnostic part, which implements the power fail safe swapping algorithm given the boundaries set by the partitions. The platform-specific part is a minimal shim that provides additional functionality such as watchdogs or supporting the nRF52 softdevice.
+
+=== FirmwareUpdater
+
+The `FirmwareUpdater` is an object for conveniently flashing firmware to the DFU partition and subsequently marking it as being ready for swapping with the active partition on the next reset. Its principle methods are `write_firmware`, which is called once per the size of the flash "write block" (typically 4KiB), and `mark_updated`, which is the final call.
+
+=== Verification
+
+The bootloader supports the verification of firmware that has been flashed to the DFU partition. Verification requires that firmware has been signed digitally using link:https://ed25519.cr.yp.to/[`ed25519`] signatures. With verification enabled, the `FirmwareUpdater::verify_and_mark_updated` method is called in place of `mark_updated`. A public key and signature are required, along with the actual length of the firmware that has been flashed. If verification fails then the firmware will not be marked as updated and therefore be rejected.
+
+Signatures are normally conveyed with the firmware to be updated and not written to flash. How signatures are provided is a firmware responsibility.
+
+To enable verification use either the `ed25519-dalek` or `ed25519-salty` features when depending on the `embassy-boot` crate. We recommend `ed25519-salty` at this time due to its small size.
+
+==== Tips on keys and signing with ed25519
+
+Ed25519 is a public key signature system where you are responsible for keeping the private key secure. We recommend embedding the *public* key in your program so that it can be easily passed to `verify_and_mark_updated`. An example declaration of the public key in your firmware:
+
+[source, rust]
+----
+static PUBLIC_SIGNING_KEY: &[u8] = include_bytes!("key.pub");
+----
+
+Signatures are often conveyed along with firmware by appending them.
+
+Ed25519 keys can be generated by a variety of tools. We recommend link:https://man.openbsd.org/signify[`signify`] as it is in wide use to sign and verify OpenBSD distributions, and is straightforward to use.
+
+The following set of Bash commands can be used to generate public and private keys on Unix platforms, and also generate a local `key.pub` file with the `signify` file headers removed. Declare a `SECRETS_DIR` environment variable in a secure location.
+
+[source, bash]
+----
+signify -G -n -p $SECRETS_DIR/key.pub -s $SECRETS_DIR/key.sec
+tail -n1 $SECRETS_DIR/key.pub | base64 -d -i - | dd ibs=10 skip=1 > key.pub
+chmod 700 $SECRETS_DIR/key.sec
+export SECRET_SIGNING_KEY=$(tail -n1 $SECRETS_DIR/key.sec)
+----
+
+Then, to sign your firmware given a declaration of `FIRMWARE_DIR` and a firmware filename of `myfirmware`:
+
+[source, bash]
+----
+shasum -a 512 -b $FIRMWARE_DIR/myfirmware > $SECRETS_DIR/message.txt
+cat $SECRETS_DIR/message.txt | dd ibs=128 count=1 | xxd -p -r > $SECRETS_DIR/message.txt
+signify -S -s $SECRETS_DIR/key.sec -m $SECRETS_DIR/message.txt -x $SECRETS_DIR/message.txt.sig
+cp $FIRMWARE_DIR/myfirmware $FIRMWARE_DIR/myfirmware+signed
+tail -n1 $SECRETS_DIR/message.txt.sig | base64 -d -i - | dd ibs=10 skip=1 >> $FIRMWARE_DIR/myfirmware+signed
+----
+
+Remember, guard the `$SECRETS_DIR/key.sec` key as compromising it means that another party can sign your firmware.
\ No newline at end of file
diff --git a/docs/modules/ROOT/pages/getting_started.adoc b/docs/modules/ROOT/pages/getting_started.adoc
index f3492a3d0..881e449b6 100644
--- a/docs/modules/ROOT/pages/getting_started.adoc
+++ b/docs/modules/ROOT/pages/getting_started.adoc
@@ -45,11 +45,11 @@ You can run an example by opening a terminal and entering the following commands
[source, bash]
----
-cd examples/nrf
+cd examples/nrf52840
cargo run --bin blinky --release
----
-== Whats next?
+== What's next?
Congratulations, you have your first Embassy application running! Here are some alternatives on where to go from here:
diff --git a/docs/modules/ROOT/pages/layer_by_layer.adoc b/docs/modules/ROOT/pages/layer_by_layer.adoc
index a96dd9fe2..a78a64a97 100644
--- a/docs/modules/ROOT/pages/layer_by_layer.adoc
+++ b/docs/modules/ROOT/pages/layer_by_layer.adoc
@@ -8,7 +8,7 @@ The application we'll write is a simple 'push button, blink led' application, wh
== PAC version
-The PAC is the lowest API for accessing peripherals and registers, if you don't count reading/writing directly to memory addresses. It provide distinct types
+The PAC is the lowest API for accessing peripherals and registers, if you don't count reading/writing directly to memory addresses. It provides distinct types
to make accessing peripheral registers easier, but it does not prevent you from writing unsafe code.
Writing an application using the PAC directly is therefore not recommended, but if the functionality you want to use is not exposed in the upper layers, that's what you need to use.
@@ -20,13 +20,13 @@ The blinky app using PAC is shown below:
include::example$layer-by-layer/blinky-pac/src/main.rs[]
----
-As you can see, there are a lot of code needed to enable the peripheral clocks, configuring the input pins and the output pins of the application.
+As you can see, a lot of code is needed to enable the peripheral clocks and to configure the input pins and the output pins of the application.
Another downside of this application is that it is busy-looping while polling the button state. This prevents the microcontroller from utilizing any sleep mode to save power.
== HAL version
-To simplify our application, we can use the HAL instead. The HAL exposes higher level APIs that handle details such
+To simplify our application, we can use the HAL instead. The HAL exposes higher level APIs that handle details such as:
* Automatically enabling the peripheral clock when you're using the peripheral
* Deriving and applying register configuration from higher level types
@@ -39,7 +39,7 @@ The HAL example is shown below:
include::example$layer-by-layer/blinky-hal/src/main.rs[]
----
-As you can see, the application becomes a lot simpler, even without using any async code. The `Input` and `Output` hides all the details accessing the GPIO registers, and allow you to use a much simpler API to query the state of the button and toggle the LED output accordingly.
+As you can see, the application becomes a lot simpler, even without using any async code. The `Input` and `Output` types hide all the details of accessing the GPIO registers and allow you to use a much simpler API for querying the state of the button and toggling the LED output.
The same downside from the PAC example still applies though: the application is busy looping and consuming more power than necessary.
diff --git a/docs/modules/ROOT/pages/runtime.adoc b/docs/modules/ROOT/pages/runtime.adoc
index a7d6a8d0c..5096f5a43 100644
--- a/docs/modules/ROOT/pages/runtime.adoc
+++ b/docs/modules/ROOT/pages/runtime.adoc
@@ -20,7 +20,7 @@ IMPORTANT: The executor relies on tasks not blocking indefinitely, as this preve
image::embassy_executor.png[Executor model]
-If you use the `#[embassy::main]` macro in your application, it creates the `Executor` for you and spawns the main entry point as the first task. You can also create the Executor manually, and you can in fact create multiple Executors.
+If you use the `#[embassy_executor::main]` macro in your application, it creates the `Executor` for you and spawns the main entry point as the first task. You can also create the Executor manually, and you can in fact create multiple Executors.
== Interrupts
diff --git a/docs/modules/ROOT/pages/stm32.adoc b/docs/modules/ROOT/pages/stm32.adoc
index 8ed9ab04b..7bfc0592b 100644
--- a/docs/modules/ROOT/pages/stm32.adoc
+++ b/docs/modules/ROOT/pages/stm32.adoc
@@ -4,9 +4,9 @@ The link:https://github.com/embassy-rs/embassy/tree/master/embassy-stm32[Embassy
== The infinite variant problem
-STM32 microcontrollers comes in many families and flavors, and supporting all of them is a big undertaking. Embassy has taken advantage of the fact
+STM32 microcontrollers come in many families, and flavors and supporting all of them is a big undertaking. Embassy has taken advantage of the fact
that the STM32 peripheral versions are shared across chip families. Instead of re-implementing the SPI
-peripheral for every STM32 chip family, embassy have a single SPI implementation that depends on
+peripheral for every STM32 chip family, embassy has a single SPI implementation that depends on
code-generated register types that are identical for STM32 families with the same version of a given peripheral.
=== The metapac
diff --git a/embassy-boot/boot/Cargo.toml b/embassy-boot/boot/Cargo.toml
index a42f88688..415d7960f 100644
--- a/embassy-boot/boot/Cargo.toml
+++ b/embassy-boot/boot/Cargo.toml
@@ -1,25 +1,56 @@
[package]
edition = "2021"
name = "embassy-boot"
-version = "0.1.0"
-description = "Bootloader using Embassy"
+version = "0.1.1"
+description = "A lightweight bootloader supporting firmware updates in a power-fail-safe way, with trial boots and rollbacks."
+license = "MIT OR Apache-2.0"
+repository = "https://github.com/embassy-rs/embassy"
+categories = [
+ "embedded",
+ "no-std",
+ "asynchronous",
+]
[package.metadata.embassy_docs]
src_base = "https://github.com/embassy-rs/embassy/blob/embassy-boot-v$VERSION/embassy-boot/boot/src/"
src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-boot/boot/src/"
target = "thumbv7em-none-eabi"
+features = ["defmt"]
+
+[package.metadata.docs.rs]
+features = ["defmt"]
[lib]
[dependencies]
defmt = { version = "0.3", optional = true }
+digest = "0.10"
log = { version = "0.4", optional = true }
-embassy-sync = { version = "0.1.0", path = "../../embassy-sync" }
+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 = "0.3.0"
+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 }
[dev-dependencies]
log = "0.4"
env_logger = "0.9"
-rand = "0.8"
+rand = "0.7" # ed25519-dalek v1.0.1 depends on this exact version
futures = { version = "0.3", features = ["executor"] }
+sha1 = "0.10.5"
+critical-section = { version = "1.1.1", features = ["std"] }
+
+[dev-dependencies.ed25519-dalek]
+default_features = false
+features = ["rand", "std", "u32_backend"]
+
+[features]
+ed25519-dalek = ["dep:ed25519-dalek", "_verify"]
+ed25519-salty = ["dep:salty", "_verify"]
+
+nightly = ["dep:embedded-storage-async", "embassy-embedded-hal/nightly"]
+
+#Internal features
+_verify = []
diff --git a/embassy-boot/boot/README.md b/embassy-boot/boot/README.md
new file mode 100644
index 000000000..07755bc6c
--- /dev/null
+++ b/embassy-boot/boot/README.md
@@ -0,0 +1,31 @@
+# embassy-boot
+
+An [Embassy](https://embassy.dev) project.
+
+A lightweight bootloader supporting firmware updates in a power-fail-safe way, with trial boots and rollbacks.
+
+The bootloader can be used either as a library or be flashed directly with the default configuration derived from linker scripts.
+
+By design, the bootloader does not provide any network capabilities. Networking capabilities for fetching new firmware can be provided by the user application, using the bootloader as a library for updating the firmware, or by using the bootloader as a library and adding this capability yourself.
+
+## Hardware support
+
+The bootloader supports different hardware in separate crates:
+
+* `embassy-boot-nrf` - for the nRF microcontrollers.
+* `embassy-boot-rp` - for the RP2040 microcontrollers.
+* `embassy-boot-stm32` - for the STM32 microcontrollers.
+
+## Minimum supported Rust version (MSRV)
+
+`embassy-boot` is guaranteed to compile on the latest stable Rust version at the time of release. It might compile with older versions but that may change in any new patch release.
+
+## 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.
diff --git a/embassy-boot/boot/src/boot_loader.rs b/embassy-boot/boot/src/boot_loader.rs
new file mode 100644
index 000000000..a8c19197b
--- /dev/null
+++ b/embassy-boot/boot/src/boot_loader.rs
@@ -0,0 +1,421 @@
+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, NorFlashError, NorFlashErrorKind};
+
+use crate::{State, BOOT_MAGIC, STATE_ERASE_VALUE, SWAP_MAGIC};
+
+/// Errors returned by bootloader
+#[derive(PartialEq, Eq, Debug)]
+pub enum BootError {
+ /// Error from flash.
+ Flash(NorFlashErrorKind),
+ /// Invalid bootloader magic
+ BadMagic,
+}
+
+#[cfg(feature = "defmt")]
+impl defmt::Format for BootError {
+ fn format(&self, fmt: defmt::Formatter) {
+ match self {
+ BootError::Flash(_) => defmt::write!(fmt, "BootError::Flash(_)"),
+ BootError::BadMagic => defmt::write!(fmt, "BootError::BadMagic"),
+ }
+ }
+}
+
+impl From for BootError
+where
+ E: NorFlashError,
+{
+ fn from(error: E) -> Self {
+ BootError::Flash(error.kind())
+ }
+}
+
+/// 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 {
+ active: ACTIVE,
+ dfu: DFU,
+ /// 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 {
+ /// 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(config: BootLoaderConfig) -> Self {
+ Self {
+ active: config.active,
+ dfu: config.dfu,
+ state: config.state,
+ }
+ }
+
+ /// Perform necessary boot preparations like swapping images.
+ ///
+ /// The DFU partition is assumed to be 1 page bigger than the active partition for the swap
+ /// algorithm to work correctly.
+ ///
+ /// The provided aligned_buf argument must satisfy any alignment requirements
+ /// given by the partition flashes. All flash operations will use this buffer.
+ ///
+ /// SWAPPING
+ ///
+ /// Assume a flash size of 3 pages for the active partition, and 4 pages for the DFU partition.
+ /// The swap index contains the copy progress, as to allow continuation of the copy process on
+ /// power failure. The index counter is represented within 1 or more pages (depending on total
+ /// flash size), where a page X is considered swapped if index at location (X + WRITE_SIZE)
+ /// contains a zero value. This ensures that index updates can be performed atomically and
+ /// avoid a situation where the wrong index value is set (page write size is "atomic").
+ ///
+ /// +-----------+------------+--------+--------+--------+--------+
+ /// | Partition | Swap Index | Page 0 | Page 1 | Page 3 | Page 4 |
+ /// +-----------+------------+--------+--------+--------+--------+
+ /// | Active | 0 | 1 | 2 | 3 | - |
+ /// | DFU | 0 | 3 | 2 | 1 | X |
+ /// +-----------+------------+--------+--------+--------+--------+
+ ///
+ /// The algorithm starts by copying 'backwards', and after the first step, the layout is
+ /// as follows:
+ ///
+ /// +-----------+------------+--------+--------+--------+--------+
+ /// | Partition | Swap Index | Page 0 | Page 1 | Page 3 | Page 4 |
+ /// +-----------+------------+--------+--------+--------+--------+
+ /// | Active | 1 | 1 | 2 | 1 | - |
+ /// | DFU | 1 | 3 | 2 | 1 | 3 |
+ /// +-----------+------------+--------+--------+--------+--------+
+ ///
+ /// The next iteration performs the same steps
+ ///
+ /// +-----------+------------+--------+--------+--------+--------+
+ /// | Partition | Swap Index | Page 0 | Page 1 | Page 3 | Page 4 |
+ /// +-----------+------------+--------+--------+--------+--------+
+ /// | Active | 2 | 1 | 2 | 1 | - |
+ /// | DFU | 2 | 3 | 2 | 2 | 3 |
+ /// +-----------+------------+--------+--------+--------+--------+
+ ///
+ /// And again until we're done
+ ///
+ /// +-----------+------------+--------+--------+--------+--------+
+ /// | Partition | Swap Index | Page 0 | Page 1 | Page 3 | Page 4 |
+ /// +-----------+------------+--------+--------+--------+--------+
+ /// | Active | 3 | 3 | 2 | 1 | - |
+ /// | DFU | 3 | 3 | 1 | 2 | 3 |
+ /// +-----------+------------+--------+--------+--------+--------+
+ ///
+ /// REVERTING
+ ///
+ /// The reverting algorithm uses the swap index to discover that images were swapped, but that
+ /// the application failed to mark the boot successful. In this case, the revert algorithm will
+ /// run.
+ ///
+ /// The revert index is located separately from the swap index, to ensure that revert can continue
+ /// on power failure.
+ ///
+ /// The revert algorithm works forwards, by starting copying into the 'unused' DFU page at the start.
+ ///
+ /// +-----------+--------------+--------+--------+--------+--------+
+ /// | Partition | Revert Index | Page 0 | Page 1 | Page 3 | Page 4 |
+ //*/
+ /// +-----------+--------------+--------+--------+--------+--------+
+ /// | Active | 3 | 1 | 2 | 1 | - |
+ /// | DFU | 3 | 3 | 1 | 2 | 3 |
+ /// +-----------+--------------+--------+--------+--------+--------+
+ ///
+ ///
+ /// +-----------+--------------+--------+--------+--------+--------+
+ /// | Partition | Revert Index | Page 0 | Page 1 | Page 3 | Page 4 |
+ /// +-----------+--------------+--------+--------+--------+--------+
+ /// | Active | 3 | 1 | 2 | 1 | - |
+ /// | DFU | 3 | 3 | 2 | 2 | 3 |
+ /// +-----------+--------------+--------+--------+--------+--------+
+ ///
+ /// +-----------+--------------+--------+--------+--------+--------+
+ /// | Partition | Revert Index | Page 0 | Page 1 | Page 3 | Page 4 |
+ /// +-----------+--------------+--------+--------+--------+--------+
+ /// | Active | 3 | 1 | 2 | 3 | - |
+ /// | DFU | 3 | 3 | 2 | 1 | 3 |
+ /// +-----------+--------------+--------+--------+--------+--------+
+ ///
+ pub fn prepare_boot(&mut self, aligned_buf: &mut [u8]) -> Result {
+ // Ensure we have enough progress pages to store copy progress
+ 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(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(aligned_buf)? {
+ trace!("Swapping");
+ self.swap(aligned_buf)?;
+ trace!("Swapping done");
+ } else {
+ trace!("Reverting");
+ self.revert(aligned_buf)?;
+
+ let state_word = &mut aligned_buf[..STATE::WRITE_SIZE];
+
+ // Invalidate progress
+ state_word.fill(!STATE_ERASE_VALUE);
+ self.state.write(STATE::WRITE_SIZE as u32, state_word)?;
+
+ // Clear magic and progress
+ self.state.erase(0, self.state.capacity() as u32)?;
+
+ // Set magic
+ state_word.fill(BOOT_MAGIC);
+ self.state.write(0, state_word)?;
+ }
+ }
+ Ok(state)
+ }
+
+ 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, 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(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((2 + index) as u32 * write_size, state_word)?;
+
+ if state_word.iter().any(|&b| b == STATE_ERASE_VALUE) {
+ return Ok(index);
+ }
+ }
+ Ok(max_index)
+ }
+
+ 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(
+ &mut self,
+ progress_index: usize,
+ from_offset: u32,
+ to_offset: u32,
+ aligned_buf: &mut [u8],
+ ) -> Result<(), BootError> {
+ if self.current_progress(aligned_buf)? <= progress_index {
+ let page_size = Self::PAGE_SIZE as u32;
+
+ 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(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, aligned_buf)?;
+ }
+ Ok(())
+ }
+
+ fn copy_page_once_to_dfu(
+ &mut self,
+ progress_index: usize,
+ from_offset: u32,
+ to_offset: u32,
+ aligned_buf: &mut [u8],
+ ) -> Result<(), BootError> {
+ if self.current_progress(aligned_buf)? <= progress_index {
+ let page_size = Self::PAGE_SIZE as u32;
+
+ 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(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, aligned_buf)?;
+ }
+ Ok(())
+ }
+
+ 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) * 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, aligned_buf)?;
+
+ // Copy DFU page to the active page
+ 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, aligned_buf)?;
+ }
+
+ Ok(())
+ }
+
+ 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 * 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 * 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, 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)
+ } else {
+ Ok(State::Boot)
+ }
+ }
+}
+
+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);
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::mem_flash::MemFlash;
+
+ #[test]
+ #[should_panic]
+ fn test_range_asserts() {
+ 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);
+ }
+}
diff --git a/embassy-boot/boot/src/digest_adapters/ed25519_dalek.rs b/embassy-boot/boot/src/digest_adapters/ed25519_dalek.rs
new file mode 100644
index 000000000..a184d1c51
--- /dev/null
+++ b/embassy-boot/boot/src/digest_adapters/ed25519_dalek.rs
@@ -0,0 +1,30 @@
+use digest::typenum::U64;
+use digest::{FixedOutput, HashMarker, OutputSizeUser, Update};
+use ed25519_dalek::Digest as _;
+
+pub struct Sha512(ed25519_dalek::Sha512);
+
+impl Default for Sha512 {
+ fn default() -> Self {
+ Self(ed25519_dalek::Sha512::new())
+ }
+}
+
+impl Update for Sha512 {
+ fn update(&mut self, data: &[u8]) {
+ self.0.update(data)
+ }
+}
+
+impl FixedOutput for Sha512 {
+ fn finalize_into(self, out: &mut digest::Output) {
+ let result = self.0.finalize();
+ out.as_mut_slice().copy_from_slice(result.as_slice())
+ }
+}
+
+impl OutputSizeUser for Sha512 {
+ type OutputSize = U64;
+}
+
+impl HashMarker for Sha512 {}
diff --git a/embassy-boot/boot/src/digest_adapters/mod.rs b/embassy-boot/boot/src/digest_adapters/mod.rs
new file mode 100644
index 000000000..9b4b4b60c
--- /dev/null
+++ b/embassy-boot/boot/src/digest_adapters/mod.rs
@@ -0,0 +1,5 @@
+#[cfg(feature = "ed25519-dalek")]
+pub(crate) mod ed25519_dalek;
+
+#[cfg(feature = "ed25519-salty")]
+pub(crate) mod salty;
diff --git a/embassy-boot/boot/src/digest_adapters/salty.rs b/embassy-boot/boot/src/digest_adapters/salty.rs
new file mode 100644
index 000000000..2b5dcf3af
--- /dev/null
+++ b/embassy-boot/boot/src/digest_adapters/salty.rs
@@ -0,0 +1,29 @@
+use digest::typenum::U64;
+use digest::{FixedOutput, HashMarker, OutputSizeUser, Update};
+
+pub struct Sha512(salty::Sha512);
+
+impl Default for Sha512 {
+ fn default() -> Self {
+ Self(salty::Sha512::new())
+ }
+}
+
+impl Update for Sha512 {
+ fn update(&mut self, data: &[u8]) {
+ self.0.update(data)
+ }
+}
+
+impl FixedOutput for Sha512 {
+ fn finalize_into(self, out: &mut digest::Output) {
+ let result = self.0.finalize();
+ out.as_mut_slice().copy_from_slice(result.as_slice())
+ }
+}
+
+impl OutputSizeUser for Sha512 {
+ type OutputSize = U64;
+}
+
+impl HashMarker for Sha512 {}
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 000000000..20731ee0a
--- /dev/null
+++ b/embassy-boot/boot/src/firmware_updater/asynch.rs
@@ -0,0 +1,299 @@
+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;
+
+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,
+}
+
+#[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
+ pub fn from_linkerfile(flash: &'a embassy_sync::mutex::Mutex) -> 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 {
+ 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,
+ }
+ }
+
+ // Make sure we are running a booted firmware to avoid reverting to a bad state.
+ async fn verify_booted(&mut self, aligned: &mut [u8]) -> Result<(), FirmwareUpdaterError> {
+ assert_eq!(aligned.len(), STATE::WRITE_SIZE);
+ if self.get_state(aligned).await? == State::Boot {
+ Ok(())
+ } else {
+ Err(FirmwareUpdaterError::BadState)
+ }
+ }
+
+ /// 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, aligned: &mut [u8]) -> Result {
+ self.state.read(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 STATE::WRITE_SIZE, and follow the alignment rules for the flash being read from
+ /// and written to.
+ #[cfg(feature = "_verify")]
+ pub async fn verify_and_mark_updated(
+ &mut self,
+ _public_key: &[u8],
+ _signature: &[u8],
+ _update_len: u32,
+ _aligned: &mut [u8],
+ ) -> Result<(), FirmwareUpdaterError> {
+ assert_eq!(_aligned.len(), STATE::WRITE_SIZE);
+ assert!(_update_len <= self.dfu.capacity() as u32);
+
+ self.verify_booted(_aligned).await?;
+
+ #[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::(_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::(_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).await
+ }
+
+ /// Verify the update in DFU with any digest.
+ pub async fn hash(
+ &mut self,
+ 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(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 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 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) -> Result<(), FirmwareUpdaterError> {
+ self.state.read(0, aligned).await?;
+
+ if aligned.iter().any(|&b| b != magic) {
+ // Read progress validity
+ 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::WRITE_SIZE as u32, aligned).await?;
+ }
+
+ // Clear magic and progress
+ self.state.erase(0, self.state.capacity() as u32).await?;
+
+ // Set magic
+ aligned.fill(magic);
+ self.state.write(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,
+ aligned: &mut [u8],
+ offset: usize,
+ data: &[u8],
+ ) -> Result<(), FirmwareUpdaterError> {
+ assert!(data.len() >= DFU::ERASE_SIZE);
+ assert_eq!(aligned.len(), STATE::WRITE_SIZE);
+
+ self.verify_booted(aligned).await?;
+
+ self.dfu.erase(offset as u32, (offset + data.len()) as u32).await?;
+
+ self.dfu.write(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.
+ ///
+ /// # Safety
+ ///
+ /// 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 prepare_update(&mut self, aligned: &mut [u8]) -> Result<&mut DFU, FirmwareUpdaterError> {
+ assert_eq!(aligned.len(), STATE::WRITE_SIZE);
+ self.verify_booted(aligned).await?;
+
+ self.dfu.erase(0, self.dfu.capacity() as u32).await?;
+
+ 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};
+
+ use super::*;
+ use crate::mem_flash::MemFlash;
+
+ #[test]
+ fn can_verify_sha1() {
+ let flash = Mutex::::new(MemFlash::<131072, 4096, 8>::default());
+ let state = Partition::new(&flash, 0, 4096);
+ let dfu = Partition::new(&flash, 65536, 65536);
+ let mut aligned = [0; 8];
+
+ 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(FirmwareUpdaterConfig { dfu, state });
+ block_on(updater.write_firmware(&mut aligned, 0, to_write.as_slice())).unwrap();
+ let mut chunk_buf = [0; 2];
+ let mut hash = [0; 20];
+ 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
new file mode 100644
index 000000000..f03f53e4d
--- /dev/null
+++ b/embassy-boot/boot/src/firmware_updater/blocking.rs
@@ -0,0 +1,302 @@
+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;
+
+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,
+}
+
+#[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
+ 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;
+ 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)
+ };
+
+ 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,
+ }
+ }
+
+ // Make sure we are running a booted firmware to avoid reverting to a bad state.
+ fn verify_booted(&mut self, aligned: &mut [u8]) -> Result<(), FirmwareUpdaterError> {
+ assert_eq!(aligned.len(), STATE::WRITE_SIZE);
+ if self.get_state(aligned)? == State::Boot {
+ Ok(())
+ } else {
+ Err(FirmwareUpdaterError::BadState)
+ }
+ }
+
+ /// 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(&mut self, aligned: &mut [u8]) -> Result {
+ self.state.read(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 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(
+ &mut self,
+ _public_key: &[u8],
+ _signature: &[u8],
+ _update_len: u32,
+ _aligned: &mut [u8],
+ ) -> Result<(), FirmwareUpdaterError> {
+ assert_eq!(_aligned.len(), STATE::WRITE_SIZE);
+ assert!(_update_len <= self.dfu.capacity() as u32);
+
+ self.verify_booted(_aligned)?;
+
+ #[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::(_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::(_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(_aligned, SWAP_MAGIC)
+ }
+
+ /// Verify the update in DFU with any digest.
+ pub fn hash(
+ &mut self,
+ 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(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 STATE::WRITE_SIZE, and follow the alignment rules for the flash being written to.
+ #[cfg(not(feature = "_verify"))]
+ 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 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(&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(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(STATE::WRITE_SIZE as u32, aligned)?;
+ }
+
+ // Clear magic and progress
+ self.state.erase(0, self.state.capacity() as u32)?;
+
+ // Set magic
+ aligned.fill(magic);
+ self.state.write(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(
+ &mut self,
+ aligned: &mut [u8],
+ offset: usize,
+ data: &[u8],
+ ) -> Result<(), FirmwareUpdaterError> {
+ assert!(data.len() >= DFU::ERASE_SIZE);
+ assert_eq!(aligned.len(), STATE::WRITE_SIZE);
+ self.verify_booted(aligned)?;
+
+ self.dfu.erase(offset as u32, (offset + data.len()) as u32)?;
+
+ self.dfu.write(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` allows for an optimized API in
+ /// exchange for added complexity.
+ ///
+ /// # Safety
+ ///
+ /// The `aligned` buffer must have a size of STATE::WRITE_SIZE, and follow the alignment rules for the flash being written to.
+ pub fn prepare_update(&mut self, aligned: &mut [u8]) -> Result<&mut DFU, FirmwareUpdaterError> {
+ assert_eq!(aligned.len(), STATE::WRITE_SIZE);
+ self.verify_booted(aligned)?;
+ self.dfu.erase(0, self.dfu.capacity() as u32)?;
+
+ 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 });
+ let mut aligned = [0; 8];
+ updater.write_firmware(&mut aligned, 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
new file mode 100644
index 000000000..55ce8f363
--- /dev/null
+++ b/embassy-boot/boot/src/firmware_updater/mod.rs
@@ -0,0 +1,51 @@
+#[cfg(feature = "nightly")]
+mod asynch;
+mod blocking;
+
+#[cfg(feature = "nightly")]
+pub use asynch::FirmwareUpdater;
+pub use blocking::BlockingFirmwareUpdater;
+use embedded_storage::nor_flash::{NorFlashError, NorFlashErrorKind};
+
+/// 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)]
+pub enum FirmwareUpdaterError {
+ /// Error from flash.
+ Flash(NorFlashErrorKind),
+ /// Signature errors.
+ Signature(signature::Error),
+ /// Bad state.
+ BadState,
+}
+
+#[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(_)"),
+ FirmwareUpdaterError::BadState => defmt::write!(fmt, "FirmwareUpdaterError::BadState"),
+ }
+ }
+}
+
+impl From for FirmwareUpdaterError
+where
+ E: NorFlashError,
+{
+ fn from(error: E) -> Self {
+ FirmwareUpdaterError::Flash(error.kind())
+ }
+}
diff --git a/embassy-boot/boot/src/lib.rs b/embassy-boot/boot/src/lib.rs
index 51e1056cf..016362b86 100644
--- a/embassy-boot/boot/src/lib.rs
+++ b/embassy-boot/boot/src/lib.rs
@@ -1,674 +1,76 @@
-#![feature(type_alias_impl_trait)]
-#![feature(generic_associated_types)]
-#![feature(generic_const_exprs)]
-#![allow(incomplete_features)]
+#![cfg_attr(feature = "nightly", feature(async_fn_in_trait))]
#![no_std]
-///! embassy-boot is a bootloader and firmware updater for embedded devices with flash
-///! storage implemented using embedded-storage
-///!
-///! The bootloader works in conjunction with the firmware application, and only has the
-///! ability to manage two flash banks with an active and a updatable part. It implements
-///! a swap algorithm that is power-failure safe, and allows reverting to the previous
-///! version of the firmware, should the application crash and fail to mark itself as booted.
-///!
-///! This library is intended to be used by platform-specific bootloaders, such as embassy-boot-nrf,
-///! which defines the limits and flash type for that particular platform.
-///!
+#![warn(missing_docs)]
+#![doc = include_str!("../README.md")]
mod fmt;
-use embedded_storage::nor_flash::{NorFlash, NorFlashError, NorFlashErrorKind, ReadNorFlash};
-use embedded_storage_async::nor_flash::AsyncNorFlash;
+mod boot_loader;
+mod digest_adapters;
+mod firmware_updater;
+#[cfg(test)]
+mod mem_flash;
+#[cfg(test)]
+mod test_flash;
-const BOOT_MAGIC: u8 = 0xD0;
-const SWAP_MAGIC: u8 = 0xF0;
+// 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;
+pub use firmware_updater::{BlockingFirmwareUpdater, FirmwareUpdaterConfig, FirmwareUpdaterError};
-#[derive(Copy, Clone, Debug)]
-#[cfg_attr(feature = "defmt", derive(defmt::Format))]
-pub struct Partition {
- pub from: usize,
- pub to: usize,
-}
+pub(crate) const BOOT_MAGIC: u8 = 0xD0;
+pub(crate) const SWAP_MAGIC: u8 = 0xF0;
-impl Partition {
- pub const fn new(from: usize, to: usize) -> Self {
- Self { from, to }
- }
- pub const fn len(&self) -> usize {
- self.to - self.from
- }
-}
-
-#[derive(PartialEq, Debug)]
+/// The state of the bootloader after running prepare.
+#[derive(PartialEq, Eq, Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum State {
+ /// Bootloader is ready to boot the active partition.
Boot,
+ /// Bootloader has swapped the active partition with the dfu partition and will attempt boot.
Swap,
}
-#[derive(PartialEq, Debug)]
-pub enum BootError {
- Flash(NorFlashErrorKind),
- BadMagic,
-}
-
-impl From for BootError
-where
- E: NorFlashError,
-{
- fn from(error: E) -> Self {
- BootError::Flash(error.kind())
- }
-}
-
-pub trait FlashConfig {
- const BLOCK_SIZE: usize;
- const ERASE_VALUE: u8;
- type FLASH: NorFlash + ReadNorFlash;
-
- fn flash(&mut self) -> &mut Self::FLASH;
-}
-
-/// Trait defining the flash handles used for active and DFU partition
-pub trait FlashProvider {
- type STATE: FlashConfig;
- type ACTIVE: FlashConfig;
- type DFU: FlashConfig;
-
- /// 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;
-}
-
-/// BootLoader works with any flash implementing embedded_storage and can also work with
-/// different page sizes and flash write sizes.
-///
-/// The PAGE_SIZE const parameter must be a multiple of the ACTIVE and DFU page sizes.
-pub struct BootLoader {
- // Page with current state of bootloader. The state partition has the following format:
- // | Range | Description |
- // | 0 - WRITE_SIZE | Magic indicating bootloader state. BOOT_MAGIC means boot, SWAP_MAGIC means swap. |
- // | WRITE_SIZE - 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,
-}
-
-impl BootLoader {
- pub fn new(active: Partition, dfu: Partition, state: Partition) -> Self {
- assert_eq!(active.len() % PAGE_SIZE, 0);
- assert_eq!(dfu.len() % PAGE_SIZE, 0);
- // DFU partition must have an extra page
- assert!(dfu.len() - active.len() >= PAGE_SIZE);
- Self { active, dfu, state }
- }
-
- pub fn boot_address(&self) -> usize {
- self.active.from
- }
-
- /// Perform necessary boot preparations like swapping images.
- ///
- /// The DFU partition is assumed to be 1 page bigger than the active partition for the swap
- /// algorithm to work correctly.
- ///
- /// SWAPPING
- ///
- /// Assume a flash size of 3 pages for the active partition, and 4 pages for the DFU partition.
- /// The swap index contains the copy progress, as to allow continuation of the copy process on
- /// power failure. The index counter is represented within 1 or more pages (depending on total
- /// flash size), where a page X is considered swapped if index at location (X + WRITE_SIZE)
- /// contains a zero value. This ensures that index updates can be performed atomically and
- /// avoid a situation where the wrong index value is set (page write size is "atomic").
- ///
- /// +-----------+------------+--------+--------+--------+--------+
- /// | Partition | Swap Index | Page 0 | Page 1 | Page 3 | Page 4 |
- /// +-----------+------------+--------+--------+--------+--------+
- /// | Active | 0 | 1 | 2 | 3 | - |
- /// | DFU | 0 | 3 | 2 | 1 | X |
- /// +-----------+-------+--------+--------+--------+--------+
- ///
- /// The algorithm starts by copying 'backwards', and after the first step, the layout is
- /// as follows:
- ///
- /// +-----------+------------+--------+--------+--------+--------+
- /// | Partition | Swap Index | Page 0 | Page 1 | Page 3 | Page 4 |
- /// +-----------+------------+--------+--------+--------+--------+
- /// | Active | 1 | 1 | 2 | 1 | - |
- /// | DFU | 1 | 3 | 2 | 1 | 3 |
- /// +-----------+------------+--------+--------+--------+--------+
- ///
- /// The next iteration performs the same steps
- ///
- /// +-----------+------------+--------+--------+--------+--------+
- /// | Partition | Swap Index | Page 0 | Page 1 | Page 3 | Page 4 |
- /// +-----------+------------+--------+--------+--------+--------+
- /// | Active | 2 | 1 | 2 | 1 | - |
- /// | DFU | 2 | 3 | 2 | 2 | 3 |
- /// +-----------+------------+--------+--------+--------+--------+
- ///
- /// And again until we're done
- ///
- /// +-----------+------------+--------+--------+--------+--------+
- /// | Partition | Swap Index | Page 0 | Page 1 | Page 3 | Page 4 |
- /// +-----------+------------+--------+--------+--------+--------+
- /// | Active | 3 | 3 | 2 | 1 | - |
- /// | DFU | 3 | 3 | 1 | 2 | 3 |
- /// +-----------+------------+--------+--------+--------+--------+
- ///
- /// REVERTING
- ///
- /// The reverting algorithm uses the swap index to discover that images were swapped, but that
- /// the application failed to mark the boot successful. In this case, the revert algorithm will
- /// run.
- ///
- /// The revert index is located separately from the swap index, to ensure that revert can continue
- /// on power failure.
- ///
- /// The revert algorithm works forwards, by starting copying into the 'unused' DFU page at the start.
- ///
- /// +-----------+--------------+--------+--------+--------+--------+
- /// | Partition | Revert Index | Page 0 | Page 1 | Page 3 | Page 4 |
- //*/
- /// +-----------+--------------+--------+--------+--------+--------+
- /// | Active | 3 | 1 | 2 | 1 | - |
- /// | DFU | 3 | 3 | 1 | 2 | 3 |
- /// +-----------+--------------+--------+--------+--------+--------+
- ///
- ///
- /// +-----------+--------------+--------+--------+--------+--------+
- /// | Partition | Revert Index | Page 0 | Page 1 | Page 3 | Page 4 |
- /// +-----------+--------------+--------+--------+--------+--------+
- /// | Active | 3 | 1 | 2 | 1 | - |
- /// | DFU | 3 | 3 | 2 | 2 | 3 |
- /// +-----------+--------------+--------+--------+--------+--------+
- ///
- /// +-----------+--------------+--------+--------+--------+--------+
- /// | Partition | Revert Index | Page 0 | Page 1 | Page 3 | Page 4 |
- /// +-----------+--------------+--------+--------+--------+--------+
- /// | Active | 3 | 1 | 2 | 3 | - |
- /// | DFU | 3 | 3 | 2 | 1 | 3 |
- /// +-----------+--------------+--------+--------+--------+--------+
- ///
- pub fn prepare_boot(&mut self, p: &mut P) -> Result
- where
- [(); <::STATE as FlashConfig>::FLASH::WRITE_SIZE]:,
- [(); <
::ACTIVE as FlashConfig>::FLASH::ERASE_SIZE]:,
- {
- // Ensure we have enough progress pages to store copy progress
- assert!(
- self.active.len() / PAGE_SIZE
- <= (self.state.len() - <
::STATE as FlashConfig>::FLASH::WRITE_SIZE)
- / <
::STATE as FlashConfig>::FLASH::WRITE_SIZE
- );
-
- // Copy contents from partition N to active
- let state = self.read_state(p.state())?;
- match 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.state())? {
- trace!("Swapping");
- self.swap(p)?;
- trace!("Swapping done");
- } else {
- trace!("Reverting");
- self.revert(p)?;
-
- // Overwrite magic and reset progress
- let fstate = p.state().flash();
- let aligned = Aligned(
- [!P::STATE::ERASE_VALUE; <
::STATE as FlashConfig>::FLASH::WRITE_SIZE],
- );
- fstate.write(self.state.from as u32, &aligned.0)?;
- fstate.erase(self.state.from as u32, self.state.to as u32)?;
- let aligned =
- Aligned([BOOT_MAGIC; <
::STATE as FlashConfig>::FLASH::WRITE_SIZE]);
- fstate.write(self.state.from as u32, &aligned.0)?;
- }
- }
- _ => {}
- }
- Ok(state)
- }
-
- fn is_swapped(&mut self, p: &mut P) -> Result
- where
- [(); P::FLASH::WRITE_SIZE]:,
- {
- let page_count = self.active.len() / P::FLASH::ERASE_SIZE;
- let progress = self.current_progress(p)?;
-
- Ok(progress >= page_count * 2)
- }
-
- fn current_progress(&mut self, p: &mut P) -> Result
- where
- [(); P::FLASH::WRITE_SIZE]:,
- {
- let write_size = P::FLASH::WRITE_SIZE;
- let max_index = ((self.state.len() - write_size) / write_size) - 1;
- let flash = p.flash();
- let mut aligned = Aligned([!P::ERASE_VALUE; P::FLASH::WRITE_SIZE]);
- for i in 0..max_index {
- flash.read((self.state.from + write_size + i * write_size) as u32, &mut aligned.0)?;
- if aligned.0 == [P::ERASE_VALUE; P::FLASH::WRITE_SIZE] {
- return Ok(i);
- }
- }
- Ok(max_index)
- }
-
- fn update_progress(&mut self, idx: usize, p: &mut P) -> Result<(), BootError>
- where
- [(); P::FLASH::WRITE_SIZE]:,
- {
- let flash = p.flash();
- let write_size = P::FLASH::WRITE_SIZE;
- let w = self.state.from + write_size + idx * write_size;
- let aligned = Aligned([!P::ERASE_VALUE; P::FLASH::WRITE_SIZE]);
- flash.write(w as u32, &aligned.0)?;
- Ok(())
- }
-
- fn active_addr(&self, n: usize) -> usize {
- self.active.from + n * PAGE_SIZE
- }
-
- fn dfu_addr(&self, n: usize) -> usize {
- self.dfu.from + n * PAGE_SIZE
- }
-
- fn copy_page_once_to_active(
- &mut self,
- idx: usize,
- from_page: usize,
- to_page: usize,
- p: &mut P,
- ) -> Result<(), BootError>
- where
- [(); <::STATE as FlashConfig>::FLASH::WRITE_SIZE]:,
- {
- let mut buf: [u8; PAGE_SIZE] = [0; PAGE_SIZE];
- if self.current_progress(p.state())? <= idx {
- let mut offset = from_page;
- for chunk in buf.chunks_mut(P::DFU::BLOCK_SIZE) {
- p.dfu().flash().read(offset as u32, chunk)?;
- offset += chunk.len();
- }
-
- p.active().flash().erase(to_page as u32, (to_page + PAGE_SIZE) as u32)?;
-
- let mut offset = to_page;
- for chunk in buf.chunks(P::ACTIVE::BLOCK_SIZE) {
- p.active().flash().write(offset as u32, &chunk)?;
- offset += chunk.len();
- }
- self.update_progress(idx, p.state())?;
- }
- Ok(())
- }
-
- fn copy_page_once_to_dfu(
- &mut self,
- idx: usize,
- from_page: usize,
- to_page: usize,
- p: &mut P,
- ) -> Result<(), BootError>
- where
- [(); <::STATE as FlashConfig>::FLASH::WRITE_SIZE]:,
- {
- let mut buf: [u8; PAGE_SIZE] = [0; PAGE_SIZE];
- if self.current_progress(p.state())? <= idx {
- let mut offset = from_page;
- for chunk in buf.chunks_mut(P::ACTIVE::BLOCK_SIZE) {
- p.active().flash().read(offset as u32, chunk)?;
- offset += chunk.len();
- }
-
- p.dfu().flash().erase(to_page as u32, (to_page + PAGE_SIZE) as u32)?;
-
- let mut offset = to_page;
- for chunk in buf.chunks(P::DFU::BLOCK_SIZE) {
- p.dfu().flash().write(offset as u32, chunk)?;
- offset += chunk.len();
- }
- self.update_progress(idx, p.state())?;
- }
- Ok(())
- }
-
- fn swap(&mut self, p: &mut P) -> Result<(), BootError>
- where
- [(); <::STATE as FlashConfig>::FLASH::WRITE_SIZE]:,
- {
- let page_count = self.active.len() / PAGE_SIZE;
- trace!("Page count: {}", page_count);
- for page in 0..page_count {
- trace!("COPY PAGE {}", page);
- // Copy active page to the 'next' DFU page.
- let active_page = self.active_addr(page_count - 1 - page);
- let dfu_page = self.dfu_addr(page_count - page);
- //trace!("Copy active {} to dfu {}", active_page, dfu_page);
- self.copy_page_once_to_dfu(page * 2, active_page, dfu_page, p)?;
-
- // Copy DFU page to the active page
- let active_page = self.active_addr(page_count - 1 - page);
- let dfu_page = self.dfu_addr(page_count - 1 - page);
- //trace!("Copy dfy {} to active {}", dfu_page, active_page);
- self.copy_page_once_to_active(page * 2 + 1, dfu_page, active_page, p)?;
- }
-
- Ok(())
- }
-
- fn revert(&mut self, p: &mut P) -> Result<(), BootError>
- where
- [(); <::STATE as FlashConfig>::FLASH::WRITE_SIZE]:,
- {
- let page_count = self.active.len() / PAGE_SIZE;
- for page in 0..page_count {
- // Copy the bad active page to the DFU page
- let active_page = self.active_addr(page);
- let dfu_page = self.dfu_addr(page);
- self.copy_page_once_to_dfu(page_count * 2 + page * 2, active_page, dfu_page, p)?;
-
- // Copy the DFU page back to the active page
- let active_page = self.active_addr(page);
- let dfu_page = self.dfu_addr(page + 1);
- self.copy_page_once_to_active(page_count * 2 + page * 2 + 1, dfu_page, active_page, p)?;
- }
-
- Ok(())
- }
-
- fn read_state(&mut self, p: &mut P) -> Result
- where
- [(); P::FLASH::WRITE_SIZE]:,
- {
- let mut magic: [u8; P::FLASH::WRITE_SIZE] = [0; P::FLASH::WRITE_SIZE];
- let flash = p.flash();
- flash.read(self.state.from as u32, &mut magic)?;
-
- if magic == [SWAP_MAGIC; P::FLASH::WRITE_SIZE] {
- Ok(State::Swap)
- } else {
- Ok(State::Boot)
- }
- }
-}
-
-/// Convenience provider that uses a single flash for everything
-pub struct SingleFlashProvider<'a, F, const ERASE_VALUE: u8 = 0xFF>
-where
- F: NorFlash + ReadNorFlash,
-{
- config: SingleFlashConfig<'a, F, ERASE_VALUE>,
-}
-
-impl<'a, F, const ERASE_VALUE: u8> SingleFlashProvider<'a, F, ERASE_VALUE>
-where
- F: NorFlash + ReadNorFlash,
-{
- pub fn new(flash: &'a mut F) -> Self {
- Self {
- config: SingleFlashConfig { flash },
- }
- }
-}
-
-pub struct SingleFlashConfig<'a, F, const ERASE_VALUE: u8 = 0xFF>
-where
- F: NorFlash + ReadNorFlash,
-{
- flash: &'a mut F,
-}
-
-impl<'a, F> FlashProvider for SingleFlashProvider<'a, F>
-where
- F: NorFlash + ReadNorFlash,
-{
- type STATE = SingleFlashConfig<'a, F>;
- type ACTIVE = SingleFlashConfig<'a, F>;
- type DFU = SingleFlashConfig<'a, F>;
-
- fn active(&mut self) -> &mut Self::STATE {
- &mut self.config
- }
- fn dfu(&mut self) -> &mut Self::ACTIVE {
- &mut self.config
- }
- fn state(&mut self) -> &mut Self::DFU {
- &mut self.config
- }
-}
-
-impl<'a, F, const ERASE_VALUE: u8> FlashConfig for SingleFlashConfig<'a, F, ERASE_VALUE>
-where
- F: NorFlash + ReadNorFlash,
-{
- const BLOCK_SIZE: usize = F::ERASE_SIZE;
- const ERASE_VALUE: u8 = ERASE_VALUE;
- type FLASH = F;
- fn flash(&mut self) -> &mut F {
- self.flash
- }
-}
-
-/// Convenience provider that uses a single flash for everything
-pub struct MultiFlashProvider<'a, ACTIVE, STATE, DFU>
-where
- ACTIVE: NorFlash + ReadNorFlash,
- STATE: NorFlash + ReadNorFlash,
- DFU: NorFlash + ReadNorFlash,
-{
- active: SingleFlashConfig<'a, ACTIVE>,
- state: SingleFlashConfig<'a, STATE>,
- dfu: SingleFlashConfig<'a, DFU>,
-}
-
-impl<'a, ACTIVE, STATE, DFU> MultiFlashProvider<'a, ACTIVE, STATE, DFU>
-where
- ACTIVE: NorFlash + ReadNorFlash,
- STATE: NorFlash + ReadNorFlash,
- DFU: NorFlash + ReadNorFlash,
-{
- pub fn new(active: &'a mut ACTIVE, state: &'a mut STATE, dfu: &'a mut DFU) -> Self {
- Self {
- active: SingleFlashConfig { flash: active },
- state: SingleFlashConfig { flash: state },
- dfu: SingleFlashConfig { flash: dfu },
- }
- }
-}
-
-impl<'a, ACTIVE, STATE, DFU> FlashProvider for MultiFlashProvider<'a, ACTIVE, STATE, DFU>
-where
- ACTIVE: NorFlash + ReadNorFlash,
- STATE: NorFlash + ReadNorFlash,
- DFU: NorFlash + ReadNorFlash,
-{
- type STATE = SingleFlashConfig<'a, STATE>;
- type ACTIVE = SingleFlashConfig<'a, ACTIVE>;
- type DFU = SingleFlashConfig<'a, DFU>;
-
- fn active(&mut self) -> &mut Self::ACTIVE {
- &mut self.active
- }
- fn dfu(&mut self) -> &mut Self::DFU {
- &mut self.dfu
- }
- fn state(&mut self) -> &mut Self::STATE {
- &mut self.state
- }
-}
-
-/// 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,
-}
-
-// NOTE: Aligned to the largest write size supported by flash
+/// Buffer aligned to 32 byte boundary, largest known alignment requirement for embassy-boot.
#[repr(align(32))]
-pub struct Aligned([u8; N]);
+pub struct AlignedBuffer(pub [u8; N]);
-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 usize,
- &__bootloader_dfu_end as *const u32 as usize,
- )
- };
- let state = unsafe {
- Partition::new(
- &__bootloader_state_start as *const u32 as usize,
- &__bootloader_state_end as *const u32 as usize,
- )
- };
-
- 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 AsRef<[u8]> for AlignedBuffer {
+ fn as_ref(&self) -> &[u8] {
+ &self.0
}
}
-impl FirmwareUpdater {
- pub const fn new(dfu: Partition, state: Partition) -> Self {
- Self { dfu, state }
- }
-
- /// Return the length of the DFU area
- pub fn firmware_len(&self) -> usize {
- self.dfu.len()
- }
-
- /// Instruct bootloader that DFU should commence at next boot.
- /// Must be provided with an aligned buffer to use for reading and writing magic;
- pub async fn update(&mut self, flash: &mut F) -> Result<(), F::Error>
- where
- [(); F::WRITE_SIZE]:,
- {
- let mut aligned = Aligned([0; { F::WRITE_SIZE }]);
- self.set_magic(&mut aligned.0, SWAP_MAGIC, flash).await
- }
-
- /// Mark firmware boot successfully
- pub async fn mark_booted(&mut self, flash: &mut F) -> Result<(), F::Error>
- where
- [(); F::WRITE_SIZE]:,
- {
- let mut aligned = Aligned([0; { F::WRITE_SIZE }]);
- self.set_magic(&mut aligned.0, BOOT_MAGIC, flash).await
- }
-
- async fn set_magic(
- &mut self,
- aligned: &mut [u8],
- magic: u8,
- flash: &mut F,
- ) -> Result<(), F::Error> {
- flash.read(self.state.from as u32, aligned).await?;
-
- if aligned.iter().find(|&&b| b != magic).is_some() {
- aligned.fill(0);
-
- flash.write(self.state.from as u32, aligned).await?;
- flash.erase(self.state.from as u32, self.state.to as u32).await?;
-
- aligned.fill(magic);
- flash.write(self.state.from as u32, aligned).await?;
- }
- Ok(())
- }
-
- // Write to a region of the DFU page
- pub async fn write_firmware(
- &mut self,
- offset: usize,
- data: &[u8],
- flash: &mut F,
- block_size: usize,
- ) -> Result<(), F::Error> {
- assert!(data.len() >= F::ERASE_SIZE);
-
- trace!(
- "Writing firmware at offset 0x{:x} len {}",
- self.dfu.from + offset,
- data.len()
- );
-
- flash
- .erase(
- (self.dfu.from + offset) as u32,
- (self.dfu.from + offset + data.len()) as u32,
- )
- .await?;
-
- trace!(
- "Erased from {} to {}",
- self.dfu.from + offset,
- self.dfu.from + offset + data.len()
- );
-
- let mut write_offset = self.dfu.from + offset;
- for chunk in data.chunks(block_size) {
- trace!("Wrote chunk at {}: {:?}", write_offset, chunk);
- flash.write(write_offset as u32, chunk).await?;
- write_offset += chunk.len();
- }
- /*
- trace!("Wrote data, reading back for verification");
-
- let mut buf: [u8; 4096] = [0; 4096];
- let mut data_offset = 0;
- let mut read_offset = self.dfu.from + offset;
- for chunk in buf.chunks_mut(block_size) {
- flash.read(read_offset as u32, chunk).await?;
- trace!("Read chunk at {}: {:?}", read_offset, chunk);
- assert_eq!(&data[data_offset..data_offset + block_size], chunk);
- read_offset += chunk.len();
- data_offset += chunk.len();
- }
- */
-
- Ok(())
+impl AsMut<[u8]> for AlignedBuffer {
+ fn as_mut(&mut self) -> &mut [u8] {
+ &mut self.0
}
}
#[cfg(test)]
mod tests {
- use core::convert::Infallible;
- use core::future::Future;
+ #![allow(unused_imports)]
- use embedded_storage::nor_flash::ErrorType;
- use embedded_storage_async::nor_flash::AsyncReadNorFlash;
+ 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]
fn test_bad_magic() {
let mut flash = MemFlash([0xff; 131072]);
- let mut flash = SingleFlashProvider::new(&mut flash);
+ let mut flash = SingleFlashConfig::new(&mut flash);
let mut bootloader = BootLoader::<4096>::new(ACTIVE, DFU, STATE);
@@ -681,281 +83,229 @@ 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>([0xff; 131072]);
- flash.0[0..4].copy_from_slice(&[BOOT_MAGIC; 4]);
- let mut flash = SingleFlashProvider::new(&mut flash);
+ flash.state().write(0, &[BOOT_MAGIC; 4]).unwrap();
- let mut bootloader: BootLoader<4096> = BootLoader::new(ACTIVE, DFU, STATE);
+ let mut bootloader = BootLoader::new(BootLoaderConfig {
+ active: flash.active(),
+ dfu: flash.dfu(),
+ state: flash.state(),
+ });
- assert_eq!(State::Boot, bootloader.prepare_boot(&mut flash).unwrap());
+ let mut page = [0; 4096];
+ 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>([0xff; 131072]);
+ 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: [u8; ACTIVE.len()] = [rand::random::(); ACTIVE.len()];
- let update: [u8; DFU.len()] = [rand::random::(); DFU.len()];
+ const ORIGINAL: [u8; FIRMWARE_SIZE] = [0x55; FIRMWARE_SIZE];
+ const UPDATE: [u8; FIRMWARE_SIZE] = [0xAA; FIRMWARE_SIZE];
+ let mut aligned = [0; 4];
- for i in ACTIVE.from..ACTIVE.to {
- flash.0[i] = original[i - ACTIVE.from];
- }
+ block_on(flash.active().erase(0, ORIGINAL.len() as u32)).unwrap();
+ block_on(flash.active().write(0, &ORIGINAL)).unwrap();
- let mut bootloader: BootLoader<4096> = BootLoader::new(ACTIVE, DFU, STATE);
- let mut updater = FirmwareUpdater::new(DFU, STATE);
- let mut offset = 0;
- for chunk in update.chunks(4096) {
- block_on(updater.write_firmware(offset, &chunk, &mut flash, 4096)).unwrap();
- offset += chunk.len();
- }
- block_on(updater.update(&mut flash)).unwrap();
+ let mut updater = FirmwareUpdater::new(FirmwareUpdaterConfig {
+ dfu: flash.dfu(),
+ state: flash.state(),
+ });
+ block_on(updater.write_firmware(&mut aligned, 0, &UPDATE)).unwrap();
+ block_on(updater.mark_updated(&mut aligned)).unwrap();
- assert_eq!(
- State::Swap,
- bootloader
- .prepare_boot(&mut SingleFlashProvider::new(&mut flash))
- .unwrap()
- );
+ // Writing after marking updated is not allowed until marked as booted.
+ let res: Result<(), FirmwareUpdaterError> = block_on(updater.write_firmware(&mut aligned, 0, &UPDATE));
+ assert!(matches!(res, Err::<(), _>(FirmwareUpdaterError::BadState)));
- for i in ACTIVE.from..ACTIVE.to {
- assert_eq!(flash.0[i], update[i - ACTIVE.from], "Index {}", i);
- }
+ 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 page).unwrap());
+
+ 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
- for i in DFU.from + 4096..DFU.to {
- assert_eq!(flash.0[i], original[i - DFU.from - 4096], "Index {}", i);
- }
+ 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 SingleFlashProvider::new(&mut flash))
- .unwrap()
- );
+ assert_eq!(State::Swap, bootloader.prepare_boot(&mut page).unwrap());
- for i in ACTIVE.from..ACTIVE.to {
- assert_eq!(flash.0[i], original[i - ACTIVE.from], "Index {}", i);
- }
-
- // Last page is untouched
- for i in DFU.from..DFU.to - 4096 {
- assert_eq!(flash.0[i], update[i - DFU.from], "Index {}", i);
- }
+ 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)).unwrap();
- assert_eq!(
- State::Boot,
- bootloader
- .prepare_boot(&mut SingleFlashProvider::new(&mut flash))
- .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]
- 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);
+ #[cfg(all(feature = "nightly", not(feature = "_verify")))]
+ 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>([0xff; 16384]);
- let mut dfu = MemFlash::<16384, 2048, 8>([0xff; 16384]);
- let mut state = MemFlash::<4096, 128, 4>([0xff; 4096]);
+ const ORIGINAL: [u8; FIRMWARE_SIZE] = [0x55; FIRMWARE_SIZE];
+ const UPDATE: [u8; FIRMWARE_SIZE] = [0xAA; FIRMWARE_SIZE];
+ let mut aligned = [0; 4];
- let original: [u8; ACTIVE.len()] = [rand::random::(); ACTIVE.len()];
- let update: [u8; DFU.len()] = [rand::random::(); DFU.len()];
+ block_on(flash.active().erase(0, ORIGINAL.len() as u32)).unwrap();
+ block_on(flash.active().write(0, &ORIGINAL)).unwrap();
- for i in ACTIVE.from..ACTIVE.to {
- active.0[i] = original[i - ACTIVE.from];
- }
+ let mut updater = FirmwareUpdater::new(FirmwareUpdaterConfig {
+ dfu: flash.dfu(),
+ state: flash.state(),
+ });
+ block_on(updater.write_firmware(&mut aligned, 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(),
+ });
- let mut offset = 0;
- for chunk in update.chunks(2048) {
- block_on(updater.write_firmware(offset, &chunk, &mut dfu, chunk.len())).unwrap();
- offset += chunk.len();
- }
- block_on(updater.update(&mut state)).unwrap();
-
- let mut bootloader: BootLoader<4096> = BootLoader::new(ACTIVE, DFU, STATE);
- assert_eq!(
- State::Swap,
- bootloader
- .prepare_boot(&mut MultiFlashProvider::new(&mut active, &mut state, &mut dfu,))
- .unwrap()
- );
-
- for i in ACTIVE.from..ACTIVE.to {
- assert_eq!(active.0[i], update[i - ACTIVE.from], "Index {}", i);
- }
+ let mut page = [0; 4096];
+ assert_eq!(State::Swap, bootloader.prepare_boot(&mut page).unwrap());
+ 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
- for i in DFU.from + 4096..DFU.to {
- assert_eq!(dfu.0[i], original[i - DFU.from - 4096], "Index {}", i);
- }
+ flash.dfu().read(4096, &mut read_buf).unwrap();
+ assert_eq!(ORIGINAL, read_buf);
}
#[test]
- 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);
+ #[cfg(all(feature = "nightly", not(feature = "_verify")))]
+ 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(),
+ });
- let mut active = MemFlash::<16384, 2048, 4>([0xff; 16384]);
- let mut dfu = MemFlash::<16384, 4096, 8>([0xff; 16384]);
- let mut state = MemFlash::<4096, 128, 4>([0xff; 4096]);
+ const ORIGINAL: [u8; FIRMWARE_SIZE] = [0x55; FIRMWARE_SIZE];
+ const UPDATE: [u8; FIRMWARE_SIZE] = [0xAA; FIRMWARE_SIZE];
+ let mut aligned = [0; 4];
- let original: [u8; ACTIVE.len()] = [rand::random::(); ACTIVE.len()];
- let update: [u8; DFU.len()] = [rand::random::(); DFU.len()];
+ block_on(flash.active().erase(0, ORIGINAL.len() as u32)).unwrap();
+ block_on(flash.active().write(0, &ORIGINAL)).unwrap();
- for i in ACTIVE.from..ACTIVE.to {
- active.0[i] = original[i - ACTIVE.from];
- }
+ let mut updater = FirmwareUpdater::new(FirmwareUpdaterConfig {
+ dfu: flash.dfu(),
+ state: flash.state(),
+ });
+ block_on(updater.write_firmware(&mut aligned, 0, &UPDATE)).unwrap();
+ block_on(updater.mark_updated(&mut aligned)).unwrap();
- let mut updater = FirmwareUpdater::new(DFU, STATE);
-
- let mut offset = 0;
- for chunk in update.chunks(4096) {
- block_on(updater.write_firmware(offset, &chunk, &mut dfu, chunk.len())).unwrap();
- offset += chunk.len();
- }
- block_on(updater.update(&mut state)).unwrap();
-
- let mut bootloader: BootLoader<4096> = BootLoader::new(ACTIVE, DFU, STATE);
- assert_eq!(
- State::Swap,
- bootloader
- .prepare_boot(&mut MultiFlashProvider::new(&mut active, &mut state, &mut dfu,))
- .unwrap()
- );
-
- for i in ACTIVE.from..ACTIVE.to {
- assert_eq!(active.0[i], update[i - ACTIVE.from], "Index {}", i);
- }
+ 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 page).unwrap());
+ 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
- for i in DFU.from + 4096..DFU.to {
- assert_eq!(dfu.0[i], original[i - DFU.from - 4096], "Index {}", i);
- }
+ flash.dfu().read(4096, &mut read_buf).unwrap();
+ assert_eq!(ORIGINAL, read_buf);
}
- struct MemFlash([u8; SIZE]);
+ #[test]
+ #[cfg(all(feature = "nightly", feature = "_verify"))]
+ fn test_verify() {
+ // The following key setup is based on:
+ // https://docs.rs/ed25519-dalek/latest/ed25519_dalek/#example
- impl NorFlash
- for MemFlash
- {
- 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.0[i] = 0xFF;
- }
- Ok(())
- }
+ use ed25519_dalek::Keypair;
+ use rand::rngs::OsRng;
- fn write(&mut self, offset: u32, data: &[u8]) -> Result<(), Self::Error> {
- assert!(data.len() % WRITE_SIZE == 0);
- assert!(offset as usize % WRITE_SIZE == 0);
- assert!(offset as usize + data.len() <= SIZE);
+ let mut csprng = OsRng {};
+ let keypair: Keypair = Keypair::generate(&mut csprng);
- self.0[offset as usize..offset as usize + data.len()].copy_from_slice(data);
+ use ed25519_dalek::{Digest, Sha512, Signature, Signer};
+ let firmware: &[u8] = b"This are bytes that would otherwise be firmware bytes for DFU.";
+ let mut digest = Sha512::new();
+ digest.update(&firmware);
+ let message = digest.finalize();
+ let signature: Signature = keypair.sign(&message);
- Ok(())
- }
- }
+ use ed25519_dalek::PublicKey;
+ let public_key: PublicKey = keypair.public;
- impl ErrorType
- for MemFlash
- {
- type Error = Infallible;
- }
+ // Setup flash
+ let flash = BlockingTestFlash::new(BootLoaderConfig {
+ active: MemFlash::<0, 0, 0>::default(),
+ dfu: MemFlash::<4096, 4096, 4>::default(),
+ state: MemFlash::<4096, 4096, 4>::default(),
+ });
- impl ReadNorFlash
- for MemFlash
- {
- const READ_SIZE: usize = 4;
+ let firmware_len = firmware.len();
- fn read(&mut self, offset: u32, buf: &mut [u8]) -> Result<(), Self::Error> {
- let len = buf.len();
- buf[..].copy_from_slice(&self.0[offset as usize..offset as usize + len]);
- Ok(())
- }
+ let mut write_buf = [0; 4096];
+ write_buf[0..firmware_len].copy_from_slice(firmware);
+ flash.dfu().write(0, &write_buf).unwrap();
- fn capacity(&self) -> usize {
- SIZE
- }
- }
+ // On with the test
+ let flash = flash.into_async();
+ let mut updater = FirmwareUpdater::new(FirmwareUpdaterConfig {
+ dfu: flash.dfu(),
+ state: flash.state(),
+ });
- impl AsyncReadNorFlash
- for MemFlash
- {
- const READ_SIZE: usize = 4;
+ let mut aligned = [0; 4];
- type ReadFuture<'a> = impl Future