init project

This commit is contained in:
martijn 2025-04-24 12:07:40 +02:00
commit 788d9bd6ea
305 changed files with 61443 additions and 0 deletions

BIN
cyw43-firmware/43439A0.bin Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,49 @@
Permissive Binary License
Version 1.0, July 2019
Redistribution. Redistribution and use in binary form, without
modification, are permitted provided that the following conditions are
met:
1) Redistributions must reproduce the above copyright notice and the
following disclaimer in the documentation and/or other materials
provided with the distribution.
2) Unless to the extent explicitly permitted by law, no reverse
engineering, decompilation, or disassembly of this software is
permitted.
3) Redistribution as part of a software development kit must include the
accompanying file named <20>DEPENDENCIES<45> and any dependencies listed in
that file.
4) Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
Limited patent license. The copyright holders (and contributors) grant a
worldwide, non-exclusive, no-charge, royalty-free patent license to
make, have made, use, offer to sell, sell, import, and otherwise
transfer this software, where such license applies only to those patent
claims licensable by the copyright holders (and contributors) that are
necessarily infringed by this software. This patent license shall not
apply to any combinations that include this software. No hardware is
licensed hereunder.
If you institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the software
itself infringes your patent(s), then your rights granted under this
license shall terminate as of the date such litigation is filed.
DISCLAIMER. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
CONTRIBUTORS "AS IS." ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT
NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

14
cyw43-firmware/README.md Normal file
View File

@ -0,0 +1,14 @@
# WiFi + Bluetooth firmware blobs
Firmware obtained from https://github.com/georgerobotics/cyw43-driver/tree/main/firmware
Licensed under the [Infineon Permissive Binary License](./LICENSE-permissive-binary-license-1.0.txt)
## Changelog
* 2023-08-21: synced with `a1dc885` - Update 43439 fw + clm to come from `wb43439A0_7_95_49_00_combined.h` + add Bluetooth firmware
* 2023-07-28: synced with `ad3bad0` - Update 43439 fw from 7.95.55 to 7.95.62
## Notes
If you update these files, please update the lengths in the `tests/rp/src/bin/cyw43_perf.rs` test (which relies on these files running from RAM).

25
cyw43-pio/CHANGELOG.md Normal file
View File

@ -0,0 +1,25 @@
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## Unreleased
- Update embassy-rp to 0.4.0
## 0.3.0 - 2025-01-05
- Update embassy-time to 0.4.0
- Update cyw43 to 0.3.0
- Update embassy-rp to 0.3.0
## 0.2.0 - 2024-08-05
- Update to cyw43 0.2.0
- Update to embassy-rp 0.2.0
## 0.1.0 - 2024-01-11
- First release

22
cyw43-pio/Cargo.toml Normal file
View File

@ -0,0 +1,22 @@
[package]
name = "cyw43-pio"
version = "0.4.0"
edition = "2021"
description = "RP2040 PIO SPI implementation for cyw43"
keywords = ["embedded", "cyw43", "embassy-net", "embedded-hal-async", "wifi"]
categories = ["embedded", "hardware-support", "no-std", "network-programming", "asynchronous"]
license = "MIT OR Apache-2.0"
repository = "https://github.com/embassy-rs/embassy"
documentation = "https://docs.embassy.dev/cyw43-pio"
[dependencies]
cyw43 = { version = "0.3.0", path = "../cyw43" }
embassy-rp = { version = "0.4.0", path = "../embassy-rp" }
fixed = "1.23.1"
defmt = { version = "0.3", optional = true }
[package.metadata.embassy_docs]
src_base = "https://github.com/embassy-rs/embassy/blob/cyw43-pio-v$VERSION/cyw43-pio/src/"
src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/cyw43-pio/src/"
target = "thumbv6m-none-eabi"
features = ["embassy-rp/rp2040"]

3
cyw43-pio/README.md Normal file
View File

@ -0,0 +1,3 @@
# cyw43-pio
RP2040 PIO driver for the nonstandard half-duplex SPI used in the Pico W. The PIO driver offloads SPI communication with the WiFi chip and improves throughput.

245
cyw43-pio/src/lib.rs Normal file
View File

@ -0,0 +1,245 @@
#![no_std]
#![allow(async_fn_in_trait)]
#![doc = include_str!("../README.md")]
#![warn(missing_docs)]
use core::slice;
use cyw43::SpiBusCyw43;
use embassy_rp::dma::Channel;
use embassy_rp::gpio::{Drive, Level, Output, Pull, SlewRate};
use embassy_rp::pio::program::pio_asm;
use embassy_rp::pio::{Common, Config, Direction, Instance, Irq, PioPin, ShiftDirection, StateMachine};
use embassy_rp::{Peripheral, PeripheralRef};
use fixed::types::extra::U8;
use fixed::FixedU32;
/// SPI comms driven by PIO.
pub struct PioSpi<'d, PIO: Instance, const SM: usize, DMA> {
cs: Output<'d>,
sm: StateMachine<'d, PIO, SM>,
irq: Irq<'d, PIO, 0>,
dma: PeripheralRef<'d, DMA>,
wrap_target: u8,
}
/// The default clock divider that works for Pico 1 and 2 W. As well as the RM2 on rp2040 devices.
/// 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.
pub const DEFAULT_CLOCK_DIVIDER: FixedU32<U8> = FixedU32::from_bits(0x0200);
/// The overclock clock divider for the Pico 1 W. Does not work on any known RM2 devices.
/// 125mhz Pio => 62.5Mhz SPI Freq. 25% higher than theoretical maximum according to
/// data sheet, but seems to work fine.
pub const OVERCLOCK_CLOCK_DIVIDER: FixedU32<U8> = FixedU32::from_bits(0x0100);
/// The clock divider for the RM2 module. Found to be needed for the Pimoroni Pico Plus 2 W,
/// Pico Plus 2 Non w with the RM2 breakout module, and the Pico 2 with the RM2 breakout module.
pub const RM2_CLOCK_DIVIDER: FixedU32<U8> = FixedU32::from_bits(0x0300);
impl<'d, PIO, const SM: usize, DMA> PioSpi<'d, PIO, SM, DMA>
where
DMA: Channel,
PIO: Instance,
{
/// Create a new instance of PioSpi.
pub fn new<DIO, CLK>(
common: &mut Common<'d, PIO>,
mut sm: StateMachine<'d, PIO, SM>,
clock_divider: FixedU32<U8>,
irq: Irq<'d, PIO, 0>,
cs: Output<'d>,
dio: DIO,
clk: CLK,
dma: impl Peripheral<P = DMA> + 'd,
) -> Self
where
DIO: PioPin,
CLK: PioPin,
{
let loaded_program = if clock_divider < DEFAULT_CLOCK_DIVIDER {
let overclock_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"
);
common.load_program(&overclock_program.program)
} else {
let default_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"
);
common.load_program(&default_program.program)
};
let mut pin_io: embassy_rp::pio::Pin<PIO> = 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(&loaded_program, &[&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.clock_divider = clock_divider;
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: loaded_program.wrap.target,
}
}
/// Write data to peripheral and return status.
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 {
self.sm.set_x(write_bits as u32);
self.sm.set_y(read_bits as u32);
self.sm.set_pindir(0b1);
self.sm.exec_jmp(self.wrap_target);
}
self.sm.set_enable(true);
self.sm.tx().dma_push(self.dma.reborrow(), write, false).await;
let mut status = 0;
self.sm
.rx()
.dma_pull(self.dma.reborrow(), slice::from_mut(&mut status), false)
.await;
status
}
/// Send command and read response into buffer.
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!("cmd_read write={} read={}", write_bits, read_bits);
#[cfg(feature = "defmt")]
defmt::trace!("cmd_read cmd = {:02x} len = {}", cmd, read.len());
unsafe {
self.sm.set_y(read_bits as u32);
self.sm.set_x(write_bits as u32);
self.sm.set_pindir(0b1);
self.sm.exec_jmp(self.wrap_target);
}
// self.cs.set_low();
self.sm.set_enable(true);
self.sm
.tx()
.dma_push(self.dma.reborrow(), slice::from_ref(&cmd), false)
.await;
self.sm.rx().dma_pull(self.dma.reborrow(), read, false).await;
let mut status = 0;
self.sm
.rx()
.dma_pull(self.dma.reborrow(), slice::from_mut(&mut status), false)
.await;
#[cfg(feature = "defmt")]
defmt::trace!("cmd_read cmd = {:02x} len = {} read = {:08x}", cmd, read.len(), read);
status
}
}
impl<'d, PIO, const SM: usize, DMA> SpiBusCyw43 for PioSpi<'d, PIO, SM, DMA>
where
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;
}
}

30
cyw43/CHANGELOG.md Normal file
View File

@ -0,0 +1,30 @@
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## Unreleased
## 0.3.0 - 2025-01-05
- Update `embassy-time` to 0.4.0
- Add Bluetooth support.
- Add WPA3 support.
- Expand wifi security configuration options.
## 0.2.0 - 2024-08-05
- Update to new versions of embassy-{time,sync}
- Add more fields to the BssInfo packet struct #2461
- Extend the Scan API #2282
- Reuse buf to reduce stack usage #2580
- Add MAC address getter to cyw43 controller #2818
- Add function to join WPA2 network with precomputed PSK. #2885
- Add function to close soft AP. #3042
- Fixing missing re-export #3211
## 0.1.0 - 2024-01-11
- First release

48
cyw43/Cargo.toml Normal file
View File

@ -0,0 +1,48 @@
[package]
name = "cyw43"
version = "0.3.0"
edition = "2021"
description = "Rust driver for the CYW43439 WiFi chip, used in the Raspberry Pi Pico W."
keywords = ["embedded", "cyw43", "embassy-net", "embedded-hal-async", "wifi"]
categories = ["embedded", "hardware-support", "no-std", "network-programming", "asynchronous"]
license = "MIT OR Apache-2.0"
repository = "https://github.com/embassy-rs/embassy"
documentation = "https://docs.embassy.dev/cyw43"
[features]
defmt = ["dep:defmt", "heapless/defmt-03", "embassy-time/defmt", "bt-hci?/defmt", "embedded-io-async?/defmt-03"]
log = ["dep:log"]
bluetooth = ["dep:bt-hci", "dep:embedded-io-async"]
# Fetch console logs from the WiFi firmware and forward them to `log` or `defmt`.
firmware-logs = []
[dependencies]
embassy-time = { version = "0.4.0", path = "../embassy-time"}
embassy-sync = { version = "0.6.2", path = "../embassy-sync"}
embassy-futures = { version = "0.1.0", path = "../embassy-futures"}
embassy-net-driver-channel = { version = "0.3.0", path = "../embassy-net-driver-channel"}
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" }
num_enum = { version = "0.5.7", default-features = false }
heapless = "0.8.0"
# Bluetooth deps
embedded-io-async = { version = "0.6.0", optional = true }
bt-hci = { version = "0.2.0", optional = true }
[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"]
[package.metadata.docs.rs]
features = ["defmt", "firmware-logs"]

55
cyw43/README.md Normal file
View File

@ -0,0 +1,55 @@
# cyw43
Rust driver for the CYW43439 wifi+bluetooth chip. Implementation based on [Infineon/wifi-host-driver](https://github.com/Infineon/wifi-host-driver).
Works on the following boards:
- Raspberry Pi Pico W (RP2040)
- Raspberry Pi Pico 2 W (RP2350A)
- Pimoroni Pico Plus 2 W (RP2350B)
- Any board with Raspberry Pi RM2 radio module.
- Any board with the CYW43439 chip, and possibly others if the protocol is similar enough.
## Features
Working:
- WiFi support
- 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, no busy polling.
- GPIO support (for LED on the Pico W).
- Bluetooth support
- Bluetooth Classic + LE HCI commands.
- Concurrent operation with WiFi.
- Implements the [bt-hci](https://crates.io/crates/bt-hci) controller traits.
- Works with the [TrouBLE](https://github.com/embassy-rs/trouble) bluetooth LE stack. Check its repo for examples using `cyw43`.
## Running the WiFi examples
- Install `probe-rs` following the instructions at <https://probe.rs>.
- `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.

508
cyw43/src/bluetooth.rs Normal file
View File

@ -0,0 +1,508 @@
use core::cell::RefCell;
use core::future::Future;
use core::mem::MaybeUninit;
use bt_hci::transport::WithIndicator;
use bt_hci::{ControllerToHostPacket, FromHciBytes, HostToControllerPacket, PacketKind, WriteHci};
use embassy_futures::yield_now;
use embassy_sync::blocking_mutex::raw::NoopRawMutex;
use embassy_sync::zerocopy_channel;
use embassy_time::{Duration, Timer};
use embedded_hal_1::digital::OutputPin;
use crate::bus::Bus;
pub use crate::bus::SpiBusCyw43;
use crate::consts::*;
use crate::util::round_up;
use crate::{util, CHIP};
pub(crate) struct BtState {
rx: [BtPacketBuf; 4],
tx: [BtPacketBuf; 4],
inner: MaybeUninit<BtStateInnre<'static>>,
}
impl BtState {
pub const fn new() -> Self {
Self {
rx: [const { BtPacketBuf::new() }; 4],
tx: [const { BtPacketBuf::new() }; 4],
inner: MaybeUninit::uninit(),
}
}
}
struct BtStateInnre<'d> {
rx: zerocopy_channel::Channel<'d, NoopRawMutex, BtPacketBuf>,
tx: zerocopy_channel::Channel<'d, NoopRawMutex, BtPacketBuf>,
}
/// Bluetooth driver.
pub struct BtDriver<'d> {
rx: RefCell<zerocopy_channel::Receiver<'d, NoopRawMutex, BtPacketBuf>>,
tx: RefCell<zerocopy_channel::Sender<'d, NoopRawMutex, BtPacketBuf>>,
}
pub(crate) struct BtRunner<'d> {
pub(crate) tx_chan: zerocopy_channel::Receiver<'d, NoopRawMutex, BtPacketBuf>,
rx_chan: zerocopy_channel::Sender<'d, NoopRawMutex, BtPacketBuf>,
// Bluetooth circular buffers
addr: u32,
h2b_write_pointer: u32,
b2h_read_pointer: u32,
}
const BT_HCI_MTU: usize = 1024;
/// Represents a packet of size MTU.
pub(crate) struct BtPacketBuf {
pub(crate) len: usize,
pub(crate) buf: [u8; BT_HCI_MTU],
}
impl BtPacketBuf {
/// Create a new packet buffer.
pub const fn new() -> Self {
Self {
len: 0,
buf: [0; BT_HCI_MTU],
}
}
}
pub(crate) fn new<'d>(state: &'d mut BtState) -> (BtRunner<'d>, BtDriver<'d>) {
// safety: this is a self-referential struct, however:
// - it can't move while the `'d` borrow is active.
// - when the borrow ends, the dangling references inside the MaybeUninit will never be used again.
let state_uninit: *mut MaybeUninit<BtStateInnre<'d>> =
(&mut state.inner as *mut MaybeUninit<BtStateInnre<'static>>).cast();
let state = unsafe { &mut *state_uninit }.write(BtStateInnre {
rx: zerocopy_channel::Channel::new(&mut state.rx[..]),
tx: zerocopy_channel::Channel::new(&mut state.tx[..]),
});
let (rx_sender, rx_receiver) = state.rx.split();
let (tx_sender, tx_receiver) = state.tx.split();
(
BtRunner {
tx_chan: tx_receiver,
rx_chan: rx_sender,
addr: 0,
h2b_write_pointer: 0,
b2h_read_pointer: 0,
},
BtDriver {
rx: RefCell::new(rx_receiver),
tx: RefCell::new(tx_sender),
},
)
}
pub(crate) struct CybtFwCb<'a> {
pub p_next_line_start: &'a [u8],
}
pub(crate) struct HexFileData<'a> {
pub addr_mode: i32,
pub hi_addr: u16,
pub dest_addr: u32,
pub p_ds: &'a mut [u8],
}
pub(crate) fn read_firmware_patch_line(p_btfw_cb: &mut CybtFwCb, hfd: &mut HexFileData) -> u32 {
let mut abs_base_addr32 = 0;
loop {
let num_bytes = p_btfw_cb.p_next_line_start[0];
p_btfw_cb.p_next_line_start = &p_btfw_cb.p_next_line_start[1..];
let addr = (p_btfw_cb.p_next_line_start[0] as u16) << 8 | p_btfw_cb.p_next_line_start[1] as u16;
p_btfw_cb.p_next_line_start = &p_btfw_cb.p_next_line_start[2..];
let line_type = p_btfw_cb.p_next_line_start[0];
p_btfw_cb.p_next_line_start = &p_btfw_cb.p_next_line_start[1..];
if num_bytes == 0 {
break;
}
hfd.p_ds[..num_bytes as usize].copy_from_slice(&p_btfw_cb.p_next_line_start[..num_bytes as usize]);
p_btfw_cb.p_next_line_start = &p_btfw_cb.p_next_line_start[num_bytes as usize..];
match line_type {
BTFW_HEX_LINE_TYPE_EXTENDED_ADDRESS => {
hfd.hi_addr = (hfd.p_ds[0] as u16) << 8 | hfd.p_ds[1] as u16;
hfd.addr_mode = BTFW_ADDR_MODE_EXTENDED;
}
BTFW_HEX_LINE_TYPE_EXTENDED_SEGMENT_ADDRESS => {
hfd.hi_addr = (hfd.p_ds[0] as u16) << 8 | hfd.p_ds[1] as u16;
hfd.addr_mode = BTFW_ADDR_MODE_SEGMENT;
}
BTFW_HEX_LINE_TYPE_ABSOLUTE_32BIT_ADDRESS => {
abs_base_addr32 = (hfd.p_ds[0] as u32) << 24
| (hfd.p_ds[1] as u32) << 16
| (hfd.p_ds[2] as u32) << 8
| hfd.p_ds[3] as u32;
hfd.addr_mode = BTFW_ADDR_MODE_LINEAR32;
}
BTFW_HEX_LINE_TYPE_DATA => {
hfd.dest_addr = addr as u32;
match hfd.addr_mode {
BTFW_ADDR_MODE_EXTENDED => hfd.dest_addr += (hfd.hi_addr as u32) << 16,
BTFW_ADDR_MODE_SEGMENT => hfd.dest_addr += (hfd.hi_addr as u32) << 4,
BTFW_ADDR_MODE_LINEAR32 => hfd.dest_addr += abs_base_addr32,
_ => {}
}
return num_bytes as u32;
}
_ => {}
}
}
0
}
impl<'a> BtRunner<'a> {
pub(crate) async fn init_bluetooth(&mut self, bus: &mut Bus<impl OutputPin, impl SpiBusCyw43>, firmware: &[u8]) {
trace!("init_bluetooth");
bus.bp_write32(CHIP.bluetooth_base_address + BT2WLAN_PWRUP_ADDR, BT2WLAN_PWRUP_WAKE)
.await;
Timer::after(Duration::from_millis(2)).await;
self.upload_bluetooth_firmware(bus, firmware).await;
self.wait_bt_ready(bus).await;
self.init_bt_buffers(bus).await;
self.wait_bt_awake(bus).await;
self.bt_set_host_ready(bus).await;
self.bt_toggle_intr(bus).await;
}
pub(crate) async fn upload_bluetooth_firmware(
&mut self,
bus: &mut Bus<impl OutputPin, impl SpiBusCyw43>,
firmware: &[u8],
) {
// read version
let version_length = firmware[0];
let _version = &firmware[1..=version_length as usize];
// skip version + 1 extra byte as per cybt_shared_bus_driver.c
let firmware = &firmware[version_length as usize + 2..];
// buffers
let mut data_buffer: [u8; 0x100] = [0; 0x100];
let mut aligned_data_buffer: [u8; 0x100] = [0; 0x100];
// structs
let mut btfw_cb = CybtFwCb {
p_next_line_start: firmware,
};
let mut hfd = HexFileData {
addr_mode: BTFW_ADDR_MODE_EXTENDED,
hi_addr: 0,
dest_addr: 0,
p_ds: &mut data_buffer,
};
loop {
let num_fw_bytes = read_firmware_patch_line(&mut btfw_cb, &mut hfd);
if num_fw_bytes == 0 {
break;
}
let fw_bytes = &hfd.p_ds[0..num_fw_bytes as usize];
let mut dest_start_addr = hfd.dest_addr + CHIP.bluetooth_base_address;
let mut aligned_data_buffer_index: usize = 0;
// pad start
if !util::is_aligned(dest_start_addr, 4) {
let num_pad_bytes = dest_start_addr % 4;
let padded_dest_start_addr = util::round_down(dest_start_addr, 4);
let memory_value = bus.bp_read32(padded_dest_start_addr).await;
let memory_value_bytes = memory_value.to_le_bytes();
// Copy the previous memory value's bytes to the start
for i in 0..num_pad_bytes as usize {
aligned_data_buffer[aligned_data_buffer_index] = memory_value_bytes[i];
aligned_data_buffer_index += 1;
}
// Copy the firmware bytes after the padding bytes
for i in 0..num_fw_bytes as usize {
aligned_data_buffer[aligned_data_buffer_index] = fw_bytes[i];
aligned_data_buffer_index += 1;
}
dest_start_addr = padded_dest_start_addr;
} else {
// Directly copy fw_bytes into aligned_data_buffer if no start padding is required
for i in 0..num_fw_bytes as usize {
aligned_data_buffer[aligned_data_buffer_index] = fw_bytes[i];
aligned_data_buffer_index += 1;
}
}
// pad end
let mut dest_end_addr = dest_start_addr + aligned_data_buffer_index as u32;
if !util::is_aligned(dest_end_addr, 4) {
let offset = dest_end_addr % 4;
let num_pad_bytes_end = 4 - offset;
let padded_dest_end_addr = util::round_down(dest_end_addr, 4);
let memory_value = bus.bp_read32(padded_dest_end_addr).await;
let memory_value_bytes = memory_value.to_le_bytes();
// Append the necessary memory bytes to pad the end of aligned_data_buffer
for i in offset..4 {
aligned_data_buffer[aligned_data_buffer_index] = memory_value_bytes[i as usize];
aligned_data_buffer_index += 1;
}
dest_end_addr += num_pad_bytes_end;
} else {
// pad end alignment not needed
}
let buffer_to_write = &aligned_data_buffer[0..aligned_data_buffer_index as usize];
assert!(dest_start_addr % 4 == 0);
assert!(dest_end_addr % 4 == 0);
assert!(aligned_data_buffer_index % 4 == 0);
bus.bp_write(dest_start_addr, buffer_to_write).await;
}
}
pub(crate) async fn wait_bt_ready(&mut self, bus: &mut Bus<impl OutputPin, impl SpiBusCyw43>) {
trace!("wait_bt_ready");
let mut success = false;
for _ in 0..300 {
let val = bus.bp_read32(BT_CTRL_REG_ADDR).await;
trace!("BT_CTRL_REG_ADDR = {:08x}", val);
if val & BTSDIO_REG_FW_RDY_BITMASK != 0 {
success = true;
break;
}
Timer::after(Duration::from_millis(1)).await;
}
assert!(success == true);
}
pub(crate) async fn wait_bt_awake(&mut self, bus: &mut Bus<impl OutputPin, impl SpiBusCyw43>) {
trace!("wait_bt_awake");
let mut success = false;
for _ in 0..300 {
let val = bus.bp_read32(BT_CTRL_REG_ADDR).await;
trace!("BT_CTRL_REG_ADDR = {:08x}", val);
if val & BTSDIO_REG_BT_AWAKE_BITMASK != 0 {
success = true;
break;
}
Timer::after(Duration::from_millis(1)).await;
}
assert!(success == true);
}
pub(crate) async fn bt_set_host_ready(&mut self, bus: &mut Bus<impl OutputPin, impl SpiBusCyw43>) {
trace!("bt_set_host_ready");
let old_val = bus.bp_read32(HOST_CTRL_REG_ADDR).await;
// TODO: do we need to swap endianness on this read?
let new_val = old_val | BTSDIO_REG_SW_RDY_BITMASK;
bus.bp_write32(HOST_CTRL_REG_ADDR, new_val).await;
}
// TODO: use this
#[allow(dead_code)]
pub(crate) async fn bt_set_awake(&mut self, bus: &mut Bus<impl OutputPin, impl SpiBusCyw43>, awake: bool) {
trace!("bt_set_awake");
let old_val = bus.bp_read32(HOST_CTRL_REG_ADDR).await;
// TODO: do we need to swap endianness on this read?
let new_val = if awake {
old_val | BTSDIO_REG_WAKE_BT_BITMASK
} else {
old_val & !BTSDIO_REG_WAKE_BT_BITMASK
};
bus.bp_write32(HOST_CTRL_REG_ADDR, new_val).await;
}
pub(crate) async fn bt_toggle_intr(&mut self, bus: &mut Bus<impl OutputPin, impl SpiBusCyw43>) {
trace!("bt_toggle_intr");
let old_val = bus.bp_read32(HOST_CTRL_REG_ADDR).await;
// TODO: do we need to swap endianness on this read?
let new_val = old_val ^ BTSDIO_REG_DATA_VALID_BITMASK;
bus.bp_write32(HOST_CTRL_REG_ADDR, new_val).await;
}
// TODO: use this
#[allow(dead_code)]
pub(crate) async fn bt_set_intr(&mut self, bus: &mut Bus<impl OutputPin, impl SpiBusCyw43>) {
trace!("bt_set_intr");
let old_val = bus.bp_read32(HOST_CTRL_REG_ADDR).await;
let new_val = old_val | BTSDIO_REG_DATA_VALID_BITMASK;
bus.bp_write32(HOST_CTRL_REG_ADDR, new_val).await;
}
pub(crate) async fn init_bt_buffers(&mut self, bus: &mut Bus<impl OutputPin, impl SpiBusCyw43>) {
trace!("init_bt_buffers");
self.addr = bus.bp_read32(WLAN_RAM_BASE_REG_ADDR).await;
assert!(self.addr != 0);
trace!("wlan_ram_base_addr = {:08x}", self.addr);
bus.bp_write32(self.addr + BTSDIO_OFFSET_HOST2BT_IN, 0).await;
bus.bp_write32(self.addr + BTSDIO_OFFSET_HOST2BT_OUT, 0).await;
bus.bp_write32(self.addr + BTSDIO_OFFSET_BT2HOST_IN, 0).await;
bus.bp_write32(self.addr + BTSDIO_OFFSET_BT2HOST_OUT, 0).await;
}
async fn bt_bus_request(&mut self, bus: &mut Bus<impl OutputPin, impl SpiBusCyw43>) {
// TODO: CYW43_THREAD_ENTER mutex?
self.bt_set_awake(bus, true).await;
self.wait_bt_awake(bus).await;
}
pub(crate) async fn hci_write(&mut self, bus: &mut Bus<impl OutputPin, impl SpiBusCyw43>) {
self.bt_bus_request(bus).await;
// NOTE(unwrap): we only call this when we do have a packet in the queue.
let buf = self.tx_chan.try_receive().unwrap();
debug!("HCI tx: {:02x}", crate::fmt::Bytes(&buf.buf[..buf.len]));
let len = buf.len as u32 - 1; // len doesn't include hci type byte
let rounded_len = round_up(len, 4);
let total_len = 4 + rounded_len;
let read_pointer = bus.bp_read32(self.addr + BTSDIO_OFFSET_HOST2BT_OUT).await;
let available = read_pointer.wrapping_sub(self.h2b_write_pointer + 4) % BTSDIO_FWBUF_SIZE;
if available < total_len {
warn!(
"bluetooth tx queue full, retrying. len {} available {}",
total_len, available
);
yield_now().await;
return;
}
// Build header
let mut header = [0u8; 4];
header[0] = len as u8;
header[1] = (len >> 8) as u8;
header[2] = (len >> 16) as u8;
header[3] = buf.buf[0]; // HCI type byte
// Write header
let addr = self.addr + BTSDIO_OFFSET_HOST_WRITE_BUF + self.h2b_write_pointer;
bus.bp_write(addr, &header).await;
self.h2b_write_pointer = (self.h2b_write_pointer + 4) % BTSDIO_FWBUF_SIZE;
// Write payload.
let payload = &buf.buf[1..][..rounded_len as usize];
if self.h2b_write_pointer as usize + payload.len() > BTSDIO_FWBUF_SIZE as usize {
// wraparound
let n = BTSDIO_FWBUF_SIZE - self.h2b_write_pointer;
let addr = self.addr + BTSDIO_OFFSET_HOST_WRITE_BUF + self.h2b_write_pointer;
bus.bp_write(addr, &payload[..n as usize]).await;
let addr = self.addr + BTSDIO_OFFSET_HOST_WRITE_BUF;
bus.bp_write(addr, &payload[n as usize..]).await;
} else {
// no wraparound
let addr = self.addr + BTSDIO_OFFSET_HOST_WRITE_BUF + self.h2b_write_pointer;
bus.bp_write(addr, payload).await;
}
self.h2b_write_pointer = (self.h2b_write_pointer + payload.len() as u32) % BTSDIO_FWBUF_SIZE;
// Update pointer.
bus.bp_write32(self.addr + BTSDIO_OFFSET_HOST2BT_IN, self.h2b_write_pointer)
.await;
self.bt_toggle_intr(bus).await;
self.tx_chan.receive_done();
}
async fn bt_has_work(&mut self, bus: &mut Bus<impl OutputPin, impl SpiBusCyw43>) -> bool {
let int_status = bus.bp_read32(CHIP.sdiod_core_base_address + SDIO_INT_STATUS).await;
if int_status & I_HMB_FC_CHANGE != 0 {
bus.bp_write32(
CHIP.sdiod_core_base_address + SDIO_INT_STATUS,
int_status & I_HMB_FC_CHANGE,
)
.await;
return true;
}
return false;
}
pub(crate) async fn handle_irq(&mut self, bus: &mut Bus<impl OutputPin, impl SpiBusCyw43>) {
if self.bt_has_work(bus).await {
loop {
// Check if we have data.
let write_pointer = bus.bp_read32(self.addr + BTSDIO_OFFSET_BT2HOST_IN).await;
let available = write_pointer.wrapping_sub(self.b2h_read_pointer) % BTSDIO_FWBUF_SIZE;
if available == 0 {
break;
}
// read header
let mut header = [0u8; 4];
let addr = self.addr + BTSDIO_OFFSET_HOST_READ_BUF + self.b2h_read_pointer;
bus.bp_read(addr, &mut header).await;
// calc length
let len = header[0] as u32 | ((header[1]) as u32) << 8 | ((header[2]) as u32) << 16;
let rounded_len = round_up(len, 4);
if available < 4 + rounded_len {
warn!("ringbuf data not enough for a full packet?");
break;
}
self.b2h_read_pointer = (self.b2h_read_pointer + 4) % BTSDIO_FWBUF_SIZE;
// Obtain a buf from the channel.
let buf = self.rx_chan.send().await;
buf.buf[0] = header[3]; // hci packet type
let payload = &mut buf.buf[1..][..rounded_len as usize];
if self.b2h_read_pointer as usize + payload.len() > BTSDIO_FWBUF_SIZE as usize {
// wraparound
let n = BTSDIO_FWBUF_SIZE - self.b2h_read_pointer;
let addr = self.addr + BTSDIO_OFFSET_HOST_READ_BUF + self.b2h_read_pointer;
bus.bp_read(addr, &mut payload[..n as usize]).await;
let addr = self.addr + BTSDIO_OFFSET_HOST_READ_BUF;
bus.bp_read(addr, &mut payload[n as usize..]).await;
} else {
// no wraparound
let addr = self.addr + BTSDIO_OFFSET_HOST_READ_BUF + self.b2h_read_pointer;
bus.bp_read(addr, payload).await;
}
self.b2h_read_pointer = (self.b2h_read_pointer + payload.len() as u32) % BTSDIO_FWBUF_SIZE;
bus.bp_write32(self.addr + BTSDIO_OFFSET_BT2HOST_OUT, self.b2h_read_pointer)
.await;
buf.len = 1 + len as usize;
debug!("HCI rx: {:02x}", crate::fmt::Bytes(&buf.buf[..buf.len]));
self.rx_chan.send_done();
self.bt_toggle_intr(bus).await;
}
}
}
}
impl<'d> embedded_io_async::ErrorType for BtDriver<'d> {
type Error = core::convert::Infallible;
}
impl<'d> bt_hci::transport::Transport for BtDriver<'d> {
fn read<'a>(&self, rx: &'a mut [u8]) -> impl Future<Output = Result<ControllerToHostPacket<'a>, Self::Error>> {
async {
let ch = &mut *self.rx.borrow_mut();
let buf = ch.receive().await;
let n = buf.len;
assert!(n < rx.len());
rx[..n].copy_from_slice(&buf.buf[..n]);
ch.receive_done();
let kind = PacketKind::from_hci_bytes_complete(&rx[..1]).unwrap();
let (res, _) = ControllerToHostPacket::from_hci_bytes_with_kind(kind, &rx[1..n]).unwrap();
Ok(res)
}
}
/// Write a complete HCI packet from the tx buffer
fn write<T: HostToControllerPacket>(&self, val: &T) -> impl Future<Output = Result<(), Self::Error>> {
async {
let ch = &mut *self.tx.borrow_mut();
let buf = ch.send().await;
let buf_len = buf.buf.len();
let mut slice = &mut buf.buf[..];
WithIndicator::new(val).write_hci(&mut slice).unwrap();
buf.len = buf_len - slice.len();
ch.send_done();
Ok(())
}
}
}

388
cyw43/src/bus.rs Normal file
View File

@ -0,0 +1,388 @@
use embassy_futures::yield_now;
use embassy_time::Timer;
use embedded_hal_1::digital::OutputPin;
use futures::FutureExt;
use crate::consts::*;
use crate::util::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<PWR, SPI> {
backplane_window: u32,
pwr: PWR,
spi: SPI,
status: u32,
}
impl<PWR, SPI> Bus<PWR, SPI>
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, bluetooth_enabled: bool) {
// Reset
trace!("WL_REG off/on");
self.pwr.set_low().unwrap();
Timer::after_millis(20).await;
self.pwr.set_high().unwrap();
Timer::after_millis(250).await;
trace!("read REG_BUS_TEST_RO");
while self
.read32_swapped(FUNC_BUS, REG_BUS_TEST_RO)
.inspect(|v| trace!("{:#x}", v))
.await
!= FEEDBEAD
{}
trace!("write REG_BUS_TEST_RW");
self.write32_swapped(FUNC_BUS, REG_BUS_TEST_RW, TEST_PATTERN).await;
let val = self.read32_swapped(FUNC_BUS, REG_BUS_TEST_RW).await;
trace!("{:#x}", val);
assert_eq!(val, TEST_PATTERN);
trace!("read REG_BUS_CTRL");
let val = self.read32_swapped(FUNC_BUS, REG_BUS_CTRL).await;
trace!("{:#010b}", (val & 0xff));
// 32-bit word length, little endian (which is the default endianess).
// TODO: C library is uint32_t val = WORD_LENGTH_32 | HIGH_SPEED_MODE| ENDIAN_BIG | INTERRUPT_POLARITY_HIGH | WAKE_UP | 0x4 << (8 * SPI_RESPONSE_DELAY) | INTR_WITH_STATUS << (8 * SPI_STATUS_ENABLE);
trace!("write REG_BUS_CTRL");
self.write32_swapped(
FUNC_BUS,
REG_BUS_CTRL,
WORD_LENGTH_32
| HIGH_SPEED
| INTERRUPT_POLARITY_HIGH
| WAKE_UP
| 0x4 << (8 * REG_BUS_RESPONSE_DELAY)
| STATUS_ENABLE << (8 * REG_BUS_STATUS_ENABLE)
| INTR_WITH_STATUS << (8 * REG_BUS_STATUS_ENABLE),
)
.await;
trace!("read REG_BUS_CTRL");
let val = self.read8(FUNC_BUS, REG_BUS_CTRL).await;
trace!("{:#b}", val);
// TODO: C doesn't do this? i doubt it messes anything up
trace!("read REG_BUS_TEST_RO");
let val = self.read32(FUNC_BUS, REG_BUS_TEST_RO).await;
trace!("{:#x}", val);
assert_eq!(val, FEEDBEAD);
// TODO: C doesn't do this? i doubt it messes anything up
trace!("read REG_BUS_TEST_RW");
let val = self.read32(FUNC_BUS, REG_BUS_TEST_RW).await;
trace!("{:#x}", val);
assert_eq!(val, TEST_PATTERN);
trace!("write SPI_RESP_DELAY_F1 CYW43_BACKPLANE_READ_PAD_LEN_BYTES");
self.write8(FUNC_BUS, SPI_RESP_DELAY_F1, WHD_BUS_SPI_BACKPLANE_READ_PADD_SIZE)
.await;
// TODO: Make sure error interrupt bits are clear?
// cyw43_write_reg_u8(self, BUS_FUNCTION, SPI_INTERRUPT_REGISTER, DATA_UNAVAILABLE | COMMAND_ERROR | DATA_ERROR | F1_OVERFLOW) != 0)
trace!("Make sure error interrupt bits are clear");
self.write8(
FUNC_BUS,
REG_BUS_INTERRUPT,
(IRQ_DATA_UNAVAILABLE | IRQ_COMMAND_ERROR | IRQ_DATA_ERROR | IRQ_F1_OVERFLOW) as u8,
)
.await;
// Enable a selection of interrupts
// TODO: why not all of these F2_F3_FIFO_RD_UNDERFLOW | F2_F3_FIFO_WR_OVERFLOW | COMMAND_ERROR | DATA_ERROR | F2_PACKET_AVAILABLE | F1_OVERFLOW | F1_INTR
trace!("enable a selection of interrupts");
let mut val = IRQ_F2_F3_FIFO_RD_UNDERFLOW
| IRQ_F2_F3_FIFO_WR_OVERFLOW
| IRQ_COMMAND_ERROR
| IRQ_DATA_ERROR
| IRQ_F2_PACKET_AVAILABLE
| IRQ_F1_OVERFLOW;
if bluetooth_enabled {
val = val | IRQ_F1_INTR;
}
self.write16(FUNC_BUS, REG_BUS_INTERRUPT_ENABLE, val).await;
}
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[..buf.len() + 1]).await;
}
#[allow(unused)]
pub async fn bp_read(&mut self, mut addr: u32, mut data: &mut [u8]) {
trace!("bp_read addr = {:08x}", addr);
// 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]) {
trace!("bp_write addr = {:08x}", addr);
// 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 {
trace!("backplane_readn addr = {:08x} len = {}", addr, len);
self.backplane_set_window(addr).await;
let mut bus_addr = addr & BACKPLANE_ADDRESS_MASK;
if len == 4 {
bus_addr |= BACKPLANE_ADDRESS_32BIT_FLAG;
}
let val = self.readn(FUNC_BACKPLANE, bus_addr, len).await;
trace!("backplane_readn addr = {:08x} len = {} val = {:08x}", addr, len, val);
return val;
}
async fn backplane_writen(&mut self, addr: u32, val: u32, len: u32) {
trace!("backplane_writen addr = {:08x} len = {} val = {:08x}", addr, len, val);
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, func: u32, addr: u32) -> u32 {
let cmd = cmd_word(READ, INC_ADDR, func, 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, func: u32, addr: u32, val: u32) {
let cmd = cmd_word(WRITE, INC_ADDR, func, 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)
}

670
cyw43/src/consts.rs Normal file
View File

@ -0,0 +1,670 @@
#![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;
// Register addresses
pub(crate) const REG_BUS_CTRL: u32 = 0x0;
pub(crate) const REG_BUS_RESPONSE_DELAY: u32 = 0x1;
pub(crate) const REG_BUS_STATUS_ENABLE: u32 = 0x2;
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;
// SPI_BUS_CONTROL Bits
pub(crate) const WORD_LENGTH_32: u32 = 0x1;
pub(crate) const ENDIAN_BIG: u32 = 0x2;
pub(crate) const CLOCK_PHASE: u32 = 0x4;
pub(crate) const CLOCK_POLARITY: u32 = 0x8;
pub(crate) const HIGH_SPEED: u32 = 0x10;
pub(crate) const INTERRUPT_POLARITY_HIGH: u32 = 0x20;
pub(crate) const WAKE_UP: u32 = 0x80;
// SPI_STATUS_ENABLE bits
pub(crate) const STATUS_ENABLE: u32 = 0x01;
pub(crate) const INTR_WITH_STATUS: u32 = 0x02;
pub(crate) const RESP_DELAY_ALL: u32 = 0x04;
pub(crate) const DWORD_PKT_LEN_EN: u32 = 0x08;
pub(crate) const CMD_ERR_CHK_EN: u32 = 0x20;
pub(crate) const DATA_ERR_CHK_EN: u32 = 0x40;
// 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 I_HMB_SW_MASK: u32 = 0x000000f0;
pub(crate) const I_HMB_FC_CHANGE: u32 = 1 << 5;
pub(crate) const SDIO_INT_STATUS: u32 = 0x20;
pub(crate) const SDIO_INT_HOST_MASK: u32 = 0x24;
pub(crate) const SPI_F2_WATERMARK: u8 = 0x20;
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 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;
// Bluetooth firmware extraction constants.
pub(crate) const BTFW_ADDR_MODE_UNKNOWN: i32 = 0;
pub(crate) const BTFW_ADDR_MODE_EXTENDED: i32 = 1;
pub(crate) const BTFW_ADDR_MODE_SEGMENT: i32 = 2;
pub(crate) const BTFW_ADDR_MODE_LINEAR32: i32 = 3;
pub(crate) const BTFW_HEX_LINE_TYPE_DATA: u8 = 0;
pub(crate) const BTFW_HEX_LINE_TYPE_END_OF_DATA: u8 = 1;
pub(crate) const BTFW_HEX_LINE_TYPE_EXTENDED_SEGMENT_ADDRESS: u8 = 2;
pub(crate) const BTFW_HEX_LINE_TYPE_EXTENDED_ADDRESS: u8 = 4;
pub(crate) const BTFW_HEX_LINE_TYPE_ABSOLUTE_32BIT_ADDRESS: u8 = 5;
// Bluetooth constants.
pub(crate) const SPI_RESP_DELAY_F1: u32 = 0x001d;
pub(crate) const WHD_BUS_SPI_BACKPLANE_READ_PADD_SIZE: u8 = 4;
pub(crate) const BT2WLAN_PWRUP_WAKE: u32 = 3;
pub(crate) const BT2WLAN_PWRUP_ADDR: u32 = 0x640894;
pub(crate) const BT_CTRL_REG_ADDR: u32 = 0x18000c7c;
pub(crate) const HOST_CTRL_REG_ADDR: u32 = 0x18000d6c;
pub(crate) const WLAN_RAM_BASE_REG_ADDR: u32 = 0x18000d68;
pub(crate) const BTSDIO_REG_DATA_VALID_BITMASK: u32 = 1 << 1;
pub(crate) const BTSDIO_REG_BT_AWAKE_BITMASK: u32 = 1 << 8;
pub(crate) const BTSDIO_REG_WAKE_BT_BITMASK: u32 = 1 << 17;
pub(crate) const BTSDIO_REG_SW_RDY_BITMASK: u32 = 1 << 24;
pub(crate) const BTSDIO_REG_FW_RDY_BITMASK: u32 = 1 << 24;
pub(crate) const BTSDIO_FWBUF_SIZE: u32 = 0x1000;
pub(crate) const BTSDIO_OFFSET_HOST_WRITE_BUF: u32 = 0;
pub(crate) const BTSDIO_OFFSET_HOST_READ_BUF: u32 = BTSDIO_FWBUF_SIZE;
pub(crate) const BTSDIO_OFFSET_HOST2BT_IN: u32 = 0x00002000;
pub(crate) const BTSDIO_OFFSET_HOST2BT_OUT: u32 = 0x00002004;
pub(crate) const BTSDIO_OFFSET_BT2HOST_IN: u32 = 0x00002008;
pub(crate) const BTSDIO_OFFSET_BT2HOST_OUT: u32 = 0x0000200C;
// 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<EStatus> 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)
}
}
#[derive(Copy, Clone, Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[repr(u32)]
pub(crate) enum Ioctl {
GetMagic = 0,
GetVersion = 1,
Up = 2,
Down = 3,
GetLoop = 4,
SetLoop = 5,
Dump = 6,
GetMsglevel = 7,
SetMsglevel = 8,
GetPromisc = 9,
SetPromisc = 10,
GetRate = 12,
GetInstance = 14,
GetInfra = 19,
SetInfra = 20,
GetAuth = 21,
SetAuth = 22,
GetBssid = 23,
SetBssid = 24,
GetSsid = 25,
SetSsid = 26,
Restart = 27,
GetChannel = 29,
SetChannel = 30,
GetSrl = 31,
SetSrl = 32,
GetLrl = 33,
SetLrl = 34,
GetPlcphdr = 35,
SetPlcphdr = 36,
GetRadio = 37,
SetRadio = 38,
GetPhytype = 39,
DumpRate = 40,
SetRateParams = 41,
GetKey = 44,
SetKey = 45,
GetRegulatory = 46,
SetRegulatory = 47,
GetPassiveScan = 48,
SetPassiveScan = 49,
Scan = 50,
ScanResults = 51,
Disassoc = 52,
Reassoc = 53,
GetRoamTrigger = 54,
SetRoamTrigger = 55,
GetRoamDelta = 56,
SetRoamDelta = 57,
GetRoamScanPeriod = 58,
SetRoamScanPeriod = 59,
Evm = 60,
GetTxant = 61,
SetTxant = 62,
GetAntdiv = 63,
SetAntdiv = 64,
GetClosed = 67,
SetClosed = 68,
GetMaclist = 69,
SetMaclist = 70,
GetRateset = 71,
SetRateset = 72,
Longtrain = 74,
GetBcnprd = 75,
SetBcnprd = 76,
GetDtimprd = 77,
SetDtimprd = 78,
GetSrom = 79,
SetSrom = 80,
GetWepRestrict = 81,
SetWepRestrict = 82,
GetCountry = 83,
SetCountry = 84,
GetPm = 85,
SetPm = 86,
GetWake = 87,
SetWake = 88,
GetForcelink = 90,
SetForcelink = 91,
FreqAccuracy = 92,
CarrierSuppress = 93,
GetPhyreg = 94,
SetPhyreg = 95,
GetRadioreg = 96,
SetRadioreg = 97,
GetRevinfo = 98,
GetUcantdiv = 99,
SetUcantdiv = 100,
RReg = 101,
WReg = 102,
GetMacmode = 105,
SetMacmode = 106,
GetMonitor = 107,
SetMonitor = 108,
GetGmode = 109,
SetGmode = 110,
GetLegacyErp = 111,
SetLegacyErp = 112,
GetRxAnt = 113,
GetCurrRateset = 114,
GetScansuppress = 115,
SetScansuppress = 116,
GetAp = 117,
SetAp = 118,
GetEapRestrict = 119,
SetEapRestrict = 120,
ScbAuthorize = 121,
ScbDeauthorize = 122,
GetWdslist = 123,
SetWdslist = 124,
GetAtim = 125,
SetAtim = 126,
GetRssi = 127,
GetPhyantdiv = 128,
SetPhyantdiv = 129,
ApRxOnly = 130,
GetTxPathPwr = 131,
SetTxPathPwr = 132,
GetWsec = 133,
SetWsec = 134,
GetPhyNoise = 135,
GetBssInfo = 136,
GetPktcnts = 137,
GetLazywds = 138,
SetLazywds = 139,
GetBandlist = 140,
GetBand = 141,
SetBand = 142,
ScbDeauthenticate = 143,
GetShortslot = 144,
GetShortslotOverride = 145,
SetShortslotOverride = 146,
GetShortslotRestrict = 147,
SetShortslotRestrict = 148,
GetGmodeProtection = 149,
GetGmodeProtectionOverride = 150,
SetGmodeProtectionOverride = 151,
Upgrade = 152,
GetIgnoreBcns = 155,
SetIgnoreBcns = 156,
GetScbTimeout = 157,
SetScbTimeout = 158,
GetAssoclist = 159,
GetClk = 160,
SetClk = 161,
GetUp = 162,
Out = 163,
GetWpaAuth = 164,
SetWpaAuth = 165,
GetUcflags = 166,
SetUcflags = 167,
GetPwridx = 168,
SetPwridx = 169,
GetTssi = 170,
GetSupRatesetOverride = 171,
SetSupRatesetOverride = 172,
GetProtectionControl = 178,
SetProtectionControl = 179,
GetPhylist = 180,
EncryptStrength = 181,
DecryptStatus = 182,
GetKeySeq = 183,
GetScanChannelTime = 184,
SetScanChannelTime = 185,
GetScanUnassocTime = 186,
SetScanUnassocTime = 187,
GetScanHomeTime = 188,
SetScanHomeTime = 189,
GetScanNprobes = 190,
SetScanNprobes = 191,
GetPrbRespTimeout = 192,
SetPrbRespTimeout = 193,
GetAtten = 194,
SetAtten = 195,
GetShmem = 196,
SetShmem = 197,
SetWsecTest = 200,
ScbDeauthenticateForReason = 201,
TkipCountermeasures = 202,
GetPiomode = 203,
SetPiomode = 204,
SetAssocPrefer = 205,
GetAssocPrefer = 206,
SetRoamPrefer = 207,
GetRoamPrefer = 208,
SetLed = 209,
GetLed = 210,
GetInterferenceMode = 211,
SetInterferenceMode = 212,
GetChannelQa = 213,
StartChannelQa = 214,
GetChannelSel = 215,
StartChannelSel = 216,
GetValidChannels = 217,
GetFakefrag = 218,
SetFakefrag = 219,
GetPwroutPercentage = 220,
SetPwroutPercentage = 221,
SetBadFramePreempt = 222,
GetBadFramePreempt = 223,
SetLeapList = 224,
GetLeapList = 225,
GetCwmin = 226,
SetCwmin = 227,
GetCwmax = 228,
SetCwmax = 229,
GetWet = 230,
SetWet = 231,
GetPub = 232,
GetKeyPrimary = 235,
SetKeyPrimary = 236,
GetAciArgs = 238,
SetAciArgs = 239,
UnsetCallback = 240,
SetCallback = 241,
GetRadar = 242,
SetRadar = 243,
SetSpectManagment = 244,
GetSpectManagment = 245,
WdsGetRemoteHwaddr = 246,
WdsGetWpaSup = 247,
SetCsScanTimer = 248,
GetCsScanTimer = 249,
MeasureRequest = 250,
Init = 251,
SendQuiet = 252,
Keepalive = 253,
SendPwrConstraint = 254,
UpgradeStatus = 255,
CurrentPwr = 256,
GetScanPassiveTime = 257,
SetScanPassiveTime = 258,
LegacyLinkBehavior = 259,
GetChannelsInCountry = 260,
GetCountryList = 261,
GetVar = 262,
SetVar = 263,
NvramGet = 264,
NvramSet = 265,
NvramDump = 266,
Reboot = 267,
SetWsecPmk = 268,
GetAuthMode = 269,
SetAuthMode = 270,
GetWakeentry = 271,
SetWakeentry = 272,
NdconfigItem = 273,
Nvotpw = 274,
Otpw = 275,
IovBlockGet = 276,
IovModulesGet = 277,
SoftReset = 278,
GetAllowMode = 279,
SetAllowMode = 280,
GetDesiredBssid = 281,
SetDesiredBssid = 282,
DisassocMyap = 283,
GetNbands = 284,
GetBandstates = 285,
GetWlcBssInfo = 286,
GetAssocInfo = 287,
GetOidPhy = 288,
SetOidPhy = 289,
SetAssocTime = 290,
GetDesiredSsid = 291,
GetChanspec = 292,
GetAssocState = 293,
SetPhyState = 294,
GetScanPending = 295,
GetScanreqPending = 296,
GetPrevRoamReason = 297,
SetPrevRoamReason = 298,
GetBandstatesPi = 299,
GetPhyState = 300,
GetBssWpaRsn = 301,
GetBssWpa2Rsn = 302,
GetBssBcnTs = 303,
GetIntDisassoc = 304,
SetNumPeers = 305,
GetNumBss = 306,
GetWsecPmk = 318,
GetRandomBytes = 319,
}
pub(crate) const WSEC_TKIP: u32 = 0x02;
pub(crate) const WSEC_AES: u32 = 0x04;
pub(crate) const AUTH_OPEN: u32 = 0x00;
pub(crate) const AUTH_SAE: u32 = 0x03;
pub(crate) const MFP_NONE: u32 = 0;
pub(crate) const MFP_CAPABLE: u32 = 1;
pub(crate) const MFP_REQUIRED: u32 = 2;
pub(crate) const WPA_AUTH_DISABLED: u32 = 0x0000;
pub(crate) const WPA_AUTH_WPA_PSK: u32 = 0x0004;
pub(crate) const WPA_AUTH_WPA2_PSK: u32 = 0x0080;
pub(crate) const WPA_AUTH_WPA3_SAE_PSK: u32 = 0x40000;

745
cyw43/src/control.rs Normal file
View File

@ -0,0 +1,745 @@
use core::cmp::{max, min};
use core::iter::zip;
use embassy_net_driver_channel as ch;
use embassy_net_driver_channel::driver::{HardwareAddress, LinkState};
use embassy_time::{Duration, Timer};
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};
/// Control errors.
#[derive(Debug)]
pub struct Error {
/// Status code.
pub status: u32,
}
/// Multicast errors.
#[derive(Debug)]
pub enum AddMulticastAddressError {
/// Not a multicast address.
NotMulticast,
/// No free address slots.
NoFreeSlots,
}
/// Control driver.
pub struct Control<'a> {
state_ch: ch::StateRunner<'a>,
events: &'a Events,
ioctl_state: &'a IoctlState,
}
/// WiFi scan type.
#[derive(Copy, Clone, Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum ScanType {
/// Active scan: the station actively transmits probes that make APs respond.
/// Faster, but uses more power.
Active,
/// Passive scan: the station doesn't transmit any probes, just listens for beacons.
/// Slower, but uses less power.
Passive,
}
/// Scan options.
#[derive(Clone, Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[non_exhaustive]
pub struct ScanOptions {
/// SSID to scan for.
pub ssid: Option<heapless::String<32>>,
/// If set to `None`, all APs will be returned. If set to `Some`, only APs
/// with the specified BSSID will be returned.
pub bssid: Option<[u8; 6]>,
/// Number of probes to send on each channel.
pub nprobes: Option<u16>,
/// Time to spend waiting on the home channel.
pub home_time: Option<Duration>,
/// Scan type: active or passive.
pub scan_type: ScanType,
/// Period of time to wait on each channel when passive scanning.
pub dwell_time: Option<Duration>,
}
impl Default for ScanOptions {
fn default() -> Self {
Self {
ssid: None,
bssid: None,
nprobes: None,
home_time: None,
scan_type: ScanType::Passive,
dwell_time: None,
}
}
}
/// Authentication type, used in [`JoinOptions::auth`].
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum JoinAuth {
/// Open network
Open,
/// WPA only
Wpa,
/// WPA2 only
Wpa2,
/// WPA3 only
Wpa3,
/// WPA2 + WPA3
Wpa2Wpa3,
}
/// Options for [`Control::join`].
#[derive(Clone, Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[non_exhaustive]
pub struct JoinOptions<'a> {
/// Authentication type. Default `Wpa2Wpa3`.
pub auth: JoinAuth,
/// Enable TKIP encryption. Default false.
pub cipher_tkip: bool,
/// Enable AES encryption. Default true.
pub cipher_aes: bool,
/// Passphrase. Default empty.
pub passphrase: &'a [u8],
/// If false, `passphrase` is the human-readable passphrase string.
/// If true, `passphrase` is the result of applying the PBKDF2 hash to the
/// passphrase string. This makes it possible to avoid storing unhashed passwords.
///
/// This is not compatible with WPA3.
/// Default false.
pub passphrase_is_prehashed: bool,
}
impl<'a> JoinOptions<'a> {
/// Create a new `JoinOptions` for joining open networks.
pub fn new_open() -> Self {
Self {
auth: JoinAuth::Open,
cipher_tkip: false,
cipher_aes: false,
passphrase: &[],
passphrase_is_prehashed: false,
}
}
/// Create a new `JoinOptions` for joining encrypted networks.
///
/// Defaults to supporting WPA2+WPA3 with AES only, you may edit
/// the returned options to change this.
pub fn new(passphrase: &'a [u8]) -> Self {
let mut this = Self::default();
this.passphrase = passphrase;
this
}
}
impl<'a> Default for JoinOptions<'a> {
fn default() -> Self {
Self {
auth: JoinAuth::Wpa2Wpa3,
cipher_tkip: false,
cipher_aes: true,
passphrase: &[],
passphrase_is_prehashed: false,
}
}
}
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,
}
}
async fn load_clm(&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::SetVar, 0, &mut buf[..8 + 12 + chunk.len()])
.await;
}
// check clmload ok
assert_eq!(self.get_iovar_u32("clmload_status").await, 0);
}
/// Initialize WiFi controller.
pub async fn init(&mut self, clm: &[u8]) {
self.load_clm(&clm).await;
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 mac_addr = self.address().await;
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_millis(100).await;
// Set antenna to chip antenna
self.ioctl_set_u32(Ioctl::SetAntdiv, 0, 0).await;
self.set_iovar_u32("bus:txglom", 0).await;
Timer::after_millis(100).await;
//self.set_iovar_u32("apsta", 1).await; // this crashes, also we already did it before...??
//Timer::after_millis(100).await;
self.set_iovar_u32("ampdu_ba_wsize", 8).await;
Timer::after_millis(100).await;
self.set_iovar_u32("ampdu_mpdu", 4).await;
Timer::after_millis(100).await;
//self.set_iovar_u32("ampdu_rx_factor", 0).await; // this crashes
//Timer::after_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_millis(100).await;
// set wifi up
self.up().await;
Timer::after_millis(100).await;
self.ioctl_set_u32(Ioctl::SetGmode, 0, 1).await; // SET_GMODE = auto
self.ioctl_set_u32(Ioctl::SetBand, 0, 0).await; // SET_BAND = any
Timer::after_millis(100).await;
self.state_ch.set_hardware_address(HardwareAddress::Ethernet(mac_addr));
debug!("cyw43 control init done");
}
/// Set the WiFi interface up.
async fn up(&mut self) {
self.ioctl(IoctlType::Set, Ioctl::Up, 0, &mut []).await;
}
/// Set the interface down.
async fn down(&mut self) {
self.ioctl(IoctlType::Set, Ioctl::Down, 0, &mut []).await;
}
/// Set power management mode.
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(Ioctl::SetPm, 0, mode_num).await;
}
/// Join an unprotected network with the provided ssid.
pub async fn join(&mut self, ssid: &str, options: JoinOptions<'_>) -> Result<(), Error> {
self.set_iovar_u32("ampdu_ba_wsize", 8).await;
if options.auth == JoinAuth::Open {
self.ioctl_set_u32(Ioctl::SetWsec, 0, 0).await;
self.set_iovar_u32x2("bsscfg:sup_wpa", 0, 0).await;
self.ioctl_set_u32(Ioctl::SetInfra, 0, 1).await;
self.ioctl_set_u32(Ioctl::SetAuth, 0, 0).await;
self.ioctl_set_u32(Ioctl::SetWpaAuth, 0, WPA_AUTH_DISABLED).await;
} else {
let mut wsec = 0;
if options.cipher_aes {
wsec |= WSEC_AES;
}
if options.cipher_tkip {
wsec |= WSEC_TKIP;
}
self.ioctl_set_u32(Ioctl::SetWsec, 0, wsec).await;
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_millis(100).await;
let (wpa12, wpa3, auth, mfp, wpa_auth) = match options.auth {
JoinAuth::Open => unreachable!(),
JoinAuth::Wpa => (true, false, AUTH_OPEN, MFP_NONE, WPA_AUTH_WPA_PSK),
JoinAuth::Wpa2 => (true, false, AUTH_OPEN, MFP_CAPABLE, WPA_AUTH_WPA2_PSK),
JoinAuth::Wpa3 => (false, true, AUTH_SAE, MFP_REQUIRED, WPA_AUTH_WPA3_SAE_PSK),
JoinAuth::Wpa2Wpa3 => (true, true, AUTH_SAE, MFP_CAPABLE, WPA_AUTH_WPA3_SAE_PSK),
};
if wpa12 {
let mut flags = 0;
if !options.passphrase_is_prehashed {
flags |= 1;
}
let mut pfi = PassphraseInfo {
len: options.passphrase.len() as _,
flags,
passphrase: [0; 64],
};
pfi.passphrase[..options.passphrase.len()].copy_from_slice(options.passphrase);
Timer::after_millis(3).await;
self.ioctl(IoctlType::Set, Ioctl::SetWsecPmk, 0, &mut pfi.to_bytes())
.await;
}
if wpa3 {
let mut pfi = SaePassphraseInfo {
len: options.passphrase.len() as _,
passphrase: [0; 128],
};
pfi.passphrase[..options.passphrase.len()].copy_from_slice(options.passphrase);
Timer::after_millis(3).await;
self.set_iovar("sae_password", &pfi.to_bytes()).await;
}
self.ioctl_set_u32(Ioctl::SetInfra, 0, 1).await;
self.ioctl_set_u32(Ioctl::SetAuth, 0, auth).await;
self.set_iovar_u32("mfp", mfp).await;
self.ioctl_set_u32(Ioctl::SetWpaAuth, 0, wpa_auth).await;
}
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
self.ioctl(IoctlType::Set, Ioctl::SetSsid, 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 })
}
}
/// Set GPIO pin on WiFi chip.
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
}
/// Start open access point.
pub async fn start_ap_open(&mut self, ssid: &str, channel: u8) {
self.start_ap(ssid, "", Security::OPEN, channel).await;
}
/// Start WPA2 protected access point.
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.down().await;
// Turn off APSTA mode
self.set_iovar_u32("apsta", 0).await;
// Set wifi up again
self.up().await;
// Turn on AP mode
self.ioctl_set_u32(Ioctl::SetAp, 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::SetChannel, 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_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::SetWsecPmk, 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
}
/// Closes access point.
pub async fn close_ap(&mut self) {
// Stop AP
self.set_iovar_u32x2("bss", 0, 0).await; // bss = BSS_DOWN
// Turn off AP mode
self.ioctl_set_u32(Ioctl::SetAp, 0, 0).await;
// Temporarily set wifi down
self.down().await;
// Turn on APSTA mode
self.set_iovar_u32("apsta", 1).await;
// Set wifi up again
self.up().await;
}
/// Add specified address to the list of hardware addresses the device
/// listens on. The address must be a Group address (I/G bit set). Up
/// to 10 addresses are supported by the firmware. Returns the number of
/// address slots filled after adding, or an error.
pub async fn add_multicast_address(&mut self, address: [u8; 6]) -> Result<usize, AddMulticastAddressError> {
// The firmware seems to ignore non-multicast addresses, so let's
// prevent the user from adding them and wasting space.
if address[0] & 0x01 != 1 {
return Err(AddMulticastAddressError::NotMulticast);
}
let mut buf = [0; 64];
self.get_iovar("mcast_list", &mut buf).await;
let n = u32::from_le_bytes(buf[..4].try_into().unwrap()) as usize;
let (used, free) = buf[4..].split_at_mut(n * 6);
if used.chunks(6).any(|a| a == address) {
return Ok(n);
}
if free.len() < 6 {
return Err(AddMulticastAddressError::NoFreeSlots);
}
free[..6].copy_from_slice(&address);
let n = n + 1;
buf[..4].copy_from_slice(&(n as u32).to_le_bytes());
self.set_iovar_v::<80>("mcast_list", &buf).await;
Ok(n)
}
/// Retrieve the list of configured multicast hardware addresses.
pub async fn list_multicast_addresses(&mut self, result: &mut [[u8; 6]; 10]) -> usize {
let mut buf = [0; 64];
self.get_iovar("mcast_list", &mut buf).await;
let n = u32::from_le_bytes(buf[..4].try_into().unwrap()) as usize;
let used = &buf[4..][..n * 6];
for (addr, output) in zip(used.chunks(6), result.iter_mut()) {
output.copy_from_slice(addr)
}
n
}
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::<196>(name, val).await
}
async fn set_iovar_v<const BUFSIZE: usize>(&mut self, name: &str, val: &[u8]) {
debug!("iovar 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_inner(IoctlType::Set, Ioctl::SetVar, 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!("iovar 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_inner(IoctlType::Get, Ioctl::GetVar, 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: Ioctl, 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: Ioctl, iface: u32, buf: &mut [u8]) -> usize {
if kind == IoctlType::Set {
debug!("ioctl set {:?} iface {} = {:02x}", cmd, iface, Bytes(buf));
}
let n = self.ioctl_inner(kind, cmd, iface, buf).await;
n
}
async fn ioctl_inner(&mut self, kind: IoctlType, cmd: Ioctl, 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, scan_opts: ScanOptions) -> Scanner<'_> {
const SCANTYPE_ACTIVE: u8 = 0;
const SCANTYPE_PASSIVE: u8 = 1;
let dwell_time = match scan_opts.dwell_time {
None => !0,
Some(t) => {
let mut t = t.as_millis() as u32;
if t == !0 {
t = !0 - 1;
}
t
}
};
let mut active_time = !0;
let mut passive_time = !0;
let scan_type = match scan_opts.scan_type {
ScanType::Active => {
active_time = dwell_time;
SCANTYPE_ACTIVE
}
ScanType::Passive => {
passive_time = dwell_time;
SCANTYPE_PASSIVE
}
};
let scan_params = ScanParams {
version: 1,
action: 1,
sync_id: 1,
ssid_len: scan_opts.ssid.as_ref().map(|e| e.as_bytes().len() as u32).unwrap_or(0),
ssid: scan_opts
.ssid
.map(|e| {
let mut ssid = [0; 32];
ssid[..e.as_bytes().len()].copy_from_slice(e.as_bytes());
ssid
})
.unwrap_or([0; 32]),
bssid: scan_opts.bssid.unwrap_or([0xff; 6]),
bss_type: 2,
scan_type,
nprobes: scan_opts.nprobes.unwrap_or(!0).into(),
active_time,
passive_time,
home_time: scan_opts.home_time.map(|e| e.as_millis() as u32).unwrap_or(!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,
}
}
/// Leave the wifi, with which we are currently associated.
pub async fn leave(&mut self) {
self.ioctl(IoctlType::Set, Ioctl::Disassoc, 0, &mut []).await;
info!("Disassociated")
}
/// Gets the MAC address of the device
pub async fn address(&mut self) -> [u8; 6] {
let mut mac_addr = [0; 6];
assert_eq!(self.get_iovar("cur_etheraddr", &mut mac_addr).await, 6);
mac_addr
}
}
/// WiFi network scanner.
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<BssInfo> {
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();
}
}

481
cyw43/src/countries.rs Normal file
View File

@ -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 };

400
cyw43/src/events.rs Normal file
View File

@ -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<NoopRawMutex, Message, 2, 1, 1>;
pub type EventSubscriber<'a> = Subscriber<'a, NoopRawMutex, Message, 2, 1, 1>;
pub struct Events {
pub queue: EventQueue,
pub mask: SharedEventMask,
}
impl Events {
pub fn new() -> Self {
Self {
queue: EventQueue::new(),
mask: SharedEventMask::default(),
}
}
}
#[derive(Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct Status {
pub event_type: Event,
pub status: u32,
}
#[derive(Copy, Clone)]
pub enum Payload {
None,
BssInfo(BssInfo),
}
#[derive(Copy, Clone)]
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<EventMask>,
}
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)
}
}

270
cyw43/src/fmt.rs Normal file
View File

@ -0,0 +1,270 @@
#![macro_use]
#![allow(unused)]
use core::fmt::{Debug, Display, LowerHex};
#[cfg(all(feature = "defmt", feature = "log"))]
compile_error!("You may not enable both `defmt` and `log` features.");
#[collapse_debuginfo(yes)]
macro_rules! assert {
($($x:tt)*) => {
{
#[cfg(not(feature = "defmt"))]
::core::assert!($($x)*);
#[cfg(feature = "defmt")]
::defmt::assert!($($x)*);
}
};
}
#[collapse_debuginfo(yes)]
macro_rules! assert_eq {
($($x:tt)*) => {
{
#[cfg(not(feature = "defmt"))]
::core::assert_eq!($($x)*);
#[cfg(feature = "defmt")]
::defmt::assert_eq!($($x)*);
}
};
}
#[collapse_debuginfo(yes)]
macro_rules! assert_ne {
($($x:tt)*) => {
{
#[cfg(not(feature = "defmt"))]
::core::assert_ne!($($x)*);
#[cfg(feature = "defmt")]
::defmt::assert_ne!($($x)*);
}
};
}
#[collapse_debuginfo(yes)]
macro_rules! debug_assert {
($($x:tt)*) => {
{
#[cfg(not(feature = "defmt"))]
::core::debug_assert!($($x)*);
#[cfg(feature = "defmt")]
::defmt::debug_assert!($($x)*);
}
};
}
#[collapse_debuginfo(yes)]
macro_rules! debug_assert_eq {
($($x:tt)*) => {
{
#[cfg(not(feature = "defmt"))]
::core::debug_assert_eq!($($x)*);
#[cfg(feature = "defmt")]
::defmt::debug_assert_eq!($($x)*);
}
};
}
#[collapse_debuginfo(yes)]
macro_rules! debug_assert_ne {
($($x:tt)*) => {
{
#[cfg(not(feature = "defmt"))]
::core::debug_assert_ne!($($x)*);
#[cfg(feature = "defmt")]
::defmt::debug_assert_ne!($($x)*);
}
};
}
#[collapse_debuginfo(yes)]
macro_rules! todo {
($($x:tt)*) => {
{
#[cfg(not(feature = "defmt"))]
::core::todo!($($x)*);
#[cfg(feature = "defmt")]
::defmt::todo!($($x)*);
}
};
}
#[collapse_debuginfo(yes)]
macro_rules! unreachable {
($($x:tt)*) => {
{
#[cfg(not(feature = "defmt"))]
::core::unreachable!($($x)*);
#[cfg(feature = "defmt")]
::defmt::unreachable!($($x)*);
}
};
}
#[collapse_debuginfo(yes)]
macro_rules! panic {
($($x:tt)*) => {
{
#[cfg(not(feature = "defmt"))]
::core::panic!($($x)*);
#[cfg(feature = "defmt")]
::defmt::panic!($($x)*);
}
};
}
#[collapse_debuginfo(yes)]
macro_rules! trace {
($s:literal $(, $x:expr)* $(,)?) => {
{
#[cfg(feature = "log")]
::log::trace!($s $(, $x)*);
#[cfg(feature = "defmt")]
::defmt::trace!($s $(, $x)*);
#[cfg(not(any(feature = "log", feature="defmt")))]
let _ = ($( & $x ),*);
}
};
}
#[collapse_debuginfo(yes)]
macro_rules! debug {
($s:literal $(, $x:expr)* $(,)?) => {
{
#[cfg(feature = "log")]
::log::debug!($s $(, $x)*);
#[cfg(feature = "defmt")]
::defmt::debug!($s $(, $x)*);
#[cfg(not(any(feature = "log", feature="defmt")))]
let _ = ($( & $x ),*);
}
};
}
#[collapse_debuginfo(yes)]
macro_rules! info {
($s:literal $(, $x:expr)* $(,)?) => {
{
#[cfg(feature = "log")]
::log::info!($s $(, $x)*);
#[cfg(feature = "defmt")]
::defmt::info!($s $(, $x)*);
#[cfg(not(any(feature = "log", feature="defmt")))]
let _ = ($( & $x ),*);
}
};
}
#[collapse_debuginfo(yes)]
macro_rules! warn {
($s:literal $(, $x:expr)* $(,)?) => {
{
#[cfg(feature = "log")]
::log::warn!($s $(, $x)*);
#[cfg(feature = "defmt")]
::defmt::warn!($s $(, $x)*);
#[cfg(not(any(feature = "log", feature="defmt")))]
let _ = ($( & $x ),*);
}
};
}
#[collapse_debuginfo(yes)]
macro_rules! error {
($s:literal $(, $x:expr)* $(,)?) => {
{
#[cfg(feature = "log")]
::log::error!($s $(, $x)*);
#[cfg(feature = "defmt")]
::defmt::error!($s $(, $x)*);
#[cfg(not(any(feature = "log", feature="defmt")))]
let _ = ($( & $x ),*);
}
};
}
#[cfg(feature = "defmt")]
#[collapse_debuginfo(yes)]
macro_rules! unwrap {
($($x:tt)*) => {
::defmt::unwrap!($($x)*)
};
}
#[cfg(not(feature = "defmt"))]
#[collapse_debuginfo(yes)]
macro_rules! unwrap {
($arg:expr) => {
match $crate::fmt::Try::into_result($arg) {
::core::result::Result::Ok(t) => t,
::core::result::Result::Err(e) => {
::core::panic!("unwrap of `{}` failed: {:?}", ::core::stringify!($arg), e);
}
}
};
($arg:expr, $($msg:expr),+ $(,)? ) => {
match $crate::fmt::Try::into_result($arg) {
::core::result::Result::Ok(t) => t,
::core::result::Result::Err(e) => {
::core::panic!("unwrap of `{}` failed: {}: {:?}", ::core::stringify!($arg), ::core::format_args!($($msg,)*), e);
}
}
}
}
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub struct NoneError;
pub trait Try {
type Ok;
type Error;
fn into_result(self) -> Result<Self::Ok, Self::Error>;
}
impl<T> Try for Option<T> {
type Ok = T;
type Error = NoneError;
#[inline]
fn into_result(self) -> Result<T, NoneError> {
self.ok_or(NoneError)
}
}
impl<T, E> Try for Result<T, E> {
type Ok = T;
type Error = E;
#[inline]
fn into_result(self) -> Self {
self
}
}
pub(crate) 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)
}
}

123
cyw43/src/ioctl.rs Normal file
View File

@ -0,0 +1,123 @@
use core::cell::{Cell, RefCell};
use core::future::{poll_fn, Future};
use core::task::{Poll, Waker};
use embassy_sync::waitqueue::WakerRegistration;
use crate::consts::Ioctl;
use crate::fmt::Bytes;
#[derive(Clone, Copy, PartialEq, Eq)]
pub enum IoctlType {
Get = 0,
Set = 2,
}
#[derive(Clone, Copy)]
pub struct PendingIoctl {
pub buf: *mut [u8],
pub kind: IoctlType,
pub cmd: Ioctl,
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<IoctlStateInner>,
wakers: RefCell<Wakers>,
}
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 fn wait_complete(&self) -> impl Future<Output = 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
}
})
}
pub fn wait_pending(&self) -> impl Future<Output = PendingIoctl> + '_ {
poll_fn(|cx| {
if let IoctlStateInner::Pending(pending) = self.state.get() {
self.state.set(IoctlStateInner::Sent { buf: pending.buf });
Poll::Ready(pending)
} else {
self.register_runner(cx.waker());
Poll::Pending
}
})
}
pub fn cancel_ioctl(&self) {
self.state.set(IoctlStateInner::Done { resp_len: 0 });
}
pub async fn do_ioctl(&self, kind: IoctlType, cmd: Ioctl, 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");
}
}
}

299
cyw43/src/lib.rs Normal file
View File

@ -0,0 +1,299 @@
#![no_std]
#![no_main]
#![allow(async_fn_in_trait)]
#![deny(unused_must_use)]
#![doc = include_str!("../README.md")]
#![warn(missing_docs)]
// This mod MUST go first, so that the others see its macros.
pub(crate) mod fmt;
#[cfg(feature = "bluetooth")]
/// Bluetooth module.
pub mod bluetooth;
mod bus;
mod consts;
mod control;
mod countries;
mod events;
mod ioctl;
mod nvram;
mod runner;
mod structs;
mod util;
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::{
AddMulticastAddressError, Control, Error as ControlError, JoinAuth, JoinOptions, ScanOptions, ScanType, Scanner,
};
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,
bluetooth_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,
bluetooth_base_address: 0x19000000,
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,
};
/// Driver state.
pub struct State {
ioctl_state: IoctlState,
net: NetState,
#[cfg(feature = "bluetooth")]
bt: bluetooth::BtState,
}
struct NetState {
ch: ch::State<MTU, 4, 4>,
events: Events,
}
impl State {
/// Create new driver state holder.
pub fn new() -> Self {
Self {
ioctl_state: IoctlState::new(),
net: NetState {
ch: ch::State::new(),
events: Events::new(),
},
#[cfg(feature = "bluetooth")]
bt: bluetooth::BtState::new(),
}
}
}
/// Power management modes.
#[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,
}
}
}
/// Embassy-net driver.
pub type NetDriver<'a> = ch::Device<'a, MTU>;
/// Create a new instance of the CYW43 driver.
///
/// Returns a handle to the network device, control handle and a runner for driving the low level
/// stack.
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.net.ch, ch::driver::HardwareAddress::Ethernet([0; 6]));
let state_ch = ch_runner.state_runner();
let mut runner = Runner::new(
ch_runner,
Bus::new(pwr, spi),
&state.ioctl_state,
&state.net.events,
#[cfg(feature = "bluetooth")]
None,
);
runner.init(firmware, None).await;
let control = Control::new(state_ch, &state.net.events, &state.ioctl_state);
(device, control, runner)
}
/// Create a new instance of the CYW43 driver.
///
/// Returns a handle to the network device, control handle and a runner for driving the low level
/// stack.
#[cfg(feature = "bluetooth")]
pub async fn new_with_bluetooth<'a, PWR, SPI>(
state: &'a mut State,
pwr: PWR,
spi: SPI,
wifi_firmware: &[u8],
bluetooth_firmware: &[u8],
) -> (
NetDriver<'a>,
bluetooth::BtDriver<'a>,
Control<'a>,
Runner<'a, PWR, SPI>,
)
where
PWR: OutputPin,
SPI: SpiBusCyw43,
{
let (ch_runner, device) = ch::new(&mut state.net.ch, ch::driver::HardwareAddress::Ethernet([0; 6]));
let state_ch = ch_runner.state_runner();
let (bt_runner, bt_driver) = bluetooth::new(&mut state.bt);
let mut runner = Runner::new(
ch_runner,
Bus::new(pwr, spi),
&state.ioctl_state,
&state.net.events,
#[cfg(feature = "bluetooth")]
Some(bt_runner),
);
runner.init(wifi_firmware, Some(bluetooth_firmware)).await;
let control = Control::new(state_ch, &state.net.events, &state.ioctl_state);
(device, bt_driver, control, runner)
}

48
cyw43/src/nvram.rs Normal file
View File

@ -0,0 +1,48 @@
pub static NVRAM: &'static [u8] = b"
NVRAMRev=$Rev$\x00\
manfid=0x2d0\x00\
prodid=0x0727\x00\
vendid=0x14e4\x00\
devid=0x43e2\x00\
boardtype=0x0887\x00\
boardrev=0x1100\x00\
boardnum=22\x00\
macaddr=00:A0:50:b5:59:5e\x00\
sromrev=11\x00\
boardflags=0x00404001\x00\
boardflags3=0x04000000\x00\
xtalfreq=37400\x00\
nocrc=1\x00\
ag0=255\x00\
aa2g=1\x00\
ccode=ALL\x00\
pa0itssit=0x20\x00\
extpagain2g=0\x00\
pa2ga0=-168,6649,-778\x00\
AvVmid_c0=0x0,0xc8\x00\
cckpwroffset0=5\x00\
maxp2ga0=84\x00\
txpwrbckof=6\x00\
cckbw202gpo=0\x00\
legofdmbw202gpo=0x66111111\x00\
mcsbw202gpo=0x77711111\x00\
propbw202gpo=0xdd\x00\
ofdmdigfilttype=18\x00\
ofdmdigfilttypebe=18\x00\
papdmode=1\x00\
papdvalidtest=1\x00\
pacalidx2g=45\x00\
papdepsoffset=-30\x00\
papdendidx=58\x00\
ltecxmux=0\x00\
ltecxpadnum=0x0102\x00\
ltecxfnsel=0x44\x00\
ltecxgcigpio=0x01\x00\
il0macaddr=00:90:4c:c5:12:38\x00\
wl0id=0x431b\x00\
deadman_to=0xffffffff\x00\
muxenab=0x100\x00\
spurconfig=0x3\x00\
glitch_based_crsmin=1\x00\
btc_mode=1\x00\
\x00";

666
cyw43/src/runner.rs Normal file
View File

@ -0,0 +1,666 @@
use embassy_futures::select::{select4, Either4};
use embassy_net_driver_channel as ch;
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::util::slice8_mut;
use crate::{events, 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(),
}
}
}
/// Driver communicating with the WiFi chip.
pub struct Runner<'a, PWR, SPI> {
ch: ch::Runner<'a, MTU>,
pub(crate) bus: Bus<PWR, SPI>,
ioctl_state: &'a IoctlState,
ioctl_id: u16,
sdpcm_seq: u8,
sdpcm_seq_max: u8,
events: &'a Events,
#[cfg(feature = "firmware-logs")]
log: LogState,
#[cfg(feature = "bluetooth")]
pub(crate) bt: Option<crate::bluetooth::BtRunner<'a>>,
}
impl<'a, PWR, SPI> Runner<'a, PWR, SPI>
where
PWR: OutputPin,
SPI: SpiBusCyw43,
{
pub(crate) fn new(
ch: ch::Runner<'a, MTU>,
bus: Bus<PWR, SPI>,
ioctl_state: &'a IoctlState,
events: &'a Events,
#[cfg(feature = "bluetooth")] bt: Option<crate::bluetooth::BtRunner<'a>>,
) -> Self {
Self {
ch,
bus,
ioctl_state,
ioctl_id: 0,
sdpcm_seq: 0,
sdpcm_seq_max: 1,
events,
#[cfg(feature = "firmware-logs")]
log: LogState::default(),
#[cfg(feature = "bluetooth")]
bt,
}
}
pub(crate) async fn init(&mut self, wifi_fw: &[u8], bt_fw: Option<&[u8]>) {
self.bus.init(bt_fw.is_some()).await;
// Init ALP (Active Low Power) clock
debug!("init alp");
self.bus
.write8(FUNC_BACKPLANE, REG_BACKPLANE_CHIP_CLOCK_CSR, BACKPLANE_ALP_AVAIL_REQ)
.await;
debug!("set f2 watermark");
self.bus
.write8(FUNC_BACKPLANE, REG_BACKPLANE_FUNCTION2_WATERMARK, 0x10)
.await;
let watermark = self.bus.read8(FUNC_BACKPLANE, REG_BACKPLANE_FUNCTION2_WATERMARK).await;
debug!("watermark = {:02x}", watermark);
assert!(watermark == 0x10);
debug!("waiting for clock...");
while self.bus.read8(FUNC_BACKPLANE, REG_BACKPLANE_CHIP_CLOCK_CSR).await & BACKPLANE_ALP_AVAIL == 0 {}
debug!("clock ok");
// clear request for ALP
debug!("clear request for ALP");
self.bus.write8(FUNC_BACKPLANE, REG_BACKPLANE_CHIP_CLOCK_CSR, 0).await;
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_disable(Core::SOCSRAM).await; // TODO: is this needed if we reset right after?
self.core_reset(Core::SOCSRAM).await;
// this is 4343x specific stuff: Disable remap for SRAM_3
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, wifi_fw).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);
// wait until HT clock is available; takes about 29ms
debug!("wait for HT clock");
while self.bus.read8(FUNC_BACKPLANE, REG_BACKPLANE_CHIP_CLOCK_CSR).await & 0x80 == 0 {}
// "Set up the interrupt mask and enable interrupts"
debug!("setup interrupt mask");
self.bus
.bp_write32(CHIP.sdiod_core_base_address + SDIO_INT_HOST_MASK, I_HMB_SW_MASK)
.await;
// Set up the interrupt mask and enable interrupts
if bt_fw.is_some() {
debug!("bluetooth setup interrupt mask");
self.bus
.bp_write32(CHIP.sdiod_core_base_address + SDIO_INT_HOST_MASK, I_HMB_FC_CHANGE)
.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, SPI_F2_WATERMARK)
.await;
// wait for F2 to be ready
debug!("waiting for F2 to be ready...");
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
debug!("clear pad 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; // SBSDIO_HT_AVAIL_REQ
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;
#[cfg(feature = "bluetooth")]
if let Some(bt_fw) = bt_fw {
self.bt.as_mut().unwrap().init_bluetooth(&mut self.bus, bt_fw).await;
}
debug!("cyw43 runner 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;
}
}
}
/// Run the CYW43 event handling loop.
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 wifi_tx = self.ch.tx_buf();
#[cfg(feature = "bluetooth")]
let bt_tx = async {
match &mut self.bt {
Some(bt) => bt.tx_chan.receive().await,
None => core::future::pending().await,
}
};
#[cfg(not(feature = "bluetooth"))]
let bt_tx = core::future::pending::<()>();
// interrupts aren't working yet for bluetooth. Do busy-polling instead.
// Note for this to work `ev` has to go last in the `select()`. It prefers
// first futures if they're ready, so other select branches don't get starved.`
#[cfg(feature = "bluetooth")]
let ev = core::future::ready(());
#[cfg(not(feature = "bluetooth"))]
let ev = self.bus.wait_for_event();
match select4(ioctl, wifi_tx, bt_tx, ev).await {
Either4::First(PendingIoctl {
buf: iobuf,
kind,
cmd,
iface,
}) => {
self.send_ioctl(kind, cmd, iface, unsafe { &*iobuf }, &mut buf).await;
self.check_status(&mut buf).await;
}
Either4::Second(packet) => {
trace!("tx pkt {:02x}", Bytes(&packet[..packet.len().min(48)]));
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;
}
Either4::Third(_) => {
#[cfg(feature = "bluetooth")]
self.bt.as_mut().unwrap().hci_write(&mut self.bus).await;
}
Either4::Fourth(()) => {
self.handle_irq(&mut buf).await;
// If we do busy-polling, make sure to yield.
// `handle_irq` will only do a 32bit read if there's no work to do, which is really fast.
// Depending on optimization level, it is possible that the 32-bit read finishes on
// first poll, so it never yields and we starve all other tasks.
#[cfg(feature = "bluetooth")]
embassy_futures::yield_now().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;
if irq != 0 {
trace!("irq{}", FormatInterrupt(irq));
}
if irq & IRQ_F2_PACKET_AVAILABLE != 0 {
self.check_status(buf).await;
}
if irq & IRQ_DATA_UNAVAILABLE != 0 {
// this seems to be ignorable with no ill effects.
trace!("IRQ DATA_UNAVAILABLE, clearing...");
self.bus.write16(FUNC_BUS, REG_BUS_INTERRUPT, 1).await;
}
#[cfg(feature = "bluetooth")]
if let Some(bt) = &mut self.bt {
bt.handle_irq(&mut self.bus).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
.immediate_publisher()
.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: Ioctl, iface: u32, data: &[u8], buf: &mut [u32; 512]) {
let buf8 = slice8_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 as u32,
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_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_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
}
}

555
cyw43/src/structs.rs Normal file
View File

@ -0,0 +1,555 @@
use crate::events::Event;
use crate::fmt::Bytes;
macro_rules! impl_bytes {
($t:ident) => {
impl $t {
/// Bytes consumed by this type.
pub const SIZE: usize = core::mem::size_of::<Self>();
/// Convert to byte array.
#[allow(unused)]
pub fn to_bytes(&self) -> [u8; Self::SIZE] {
unsafe { core::mem::transmute(*self) }
}
/// Create from byte array.
#[allow(unused)]
pub fn from_bytes(bytes: &[u8; Self::SIZE]) -> &Self {
let alignment = core::mem::align_of::<Self>();
assert_eq!(
bytes.as_ptr().align_offset(alignment),
0,
"{} is not aligned",
core::any::type_name::<Self>()
);
unsafe { core::mem::transmute(bytes) }
}
/// Create from mutable byte array.
#[allow(unused)]
pub fn from_bytes_mut(bytes: &mut [u8; Self::SIZE]) -> &mut Self {
let alignment = core::mem::align_of::<Self>();
assert_eq!(
bytes.as_ptr().align_offset(alignment),
0,
"{} is not aligned",
core::any::type_name::<Self>()
);
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: 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 {
/// Swap endianness.
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 SaePassphraseInfo {
pub len: u16,
pub passphrase: [u8; 128],
}
impl_bytes!(SaePassphraseInfo);
#[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 {
/// Version.
pub version: u32,
/// Length.
pub length: u32,
/// BSSID.
pub bssid: [u8; 6],
/// Beacon period.
pub beacon_period: u16,
/// Capability.
pub capability: u16,
/// SSID length.
pub ssid_len: u8,
/// SSID.
pub ssid: [u8; 32],
reserved1: [u8; 1],
/// Number of rates in the rates field.
pub rateset_count: u32,
/// Rates in 500kpbs units.
pub rates: [u8; 16],
/// Channel specification.
pub chanspec: u16,
/// Announcement traffic indication message.
pub atim_window: u16,
/// Delivery traffic indication message.
pub dtim_period: u8,
reserved2: [u8; 1],
/// Receive signal strength (in dbM).
pub rssi: i16,
/// Received noise (in dbM).
pub phy_noise: i8,
/// 802.11n capability.
pub n_cap: u8,
reserved3: [u8; 2],
/// 802.11n BSS capabilities.
pub nbss_cap: u32,
/// 802.11n control channel number.
pub ctl_ch: u8,
reserved4: [u8; 3],
reserved32: [u32; 1],
/// Flags.
pub flags: u8,
/// VHT capability.
pub vht_cap: u8,
reserved5: [u8; 2],
/// 802.11n BSS required MCS.
pub basic_mcs: [u8; 16],
/// Information Elements (IE) offset.
pub ie_offset: u16,
/// Length of Information Elements (IE) in bytes.
pub ie_length: u32,
/// Average signal-to-noise (SNR) ratio during frame reception.
pub snr: i16,
// there will be more stuff here
}
impl_bytes!(BssInfo);
impl BssInfo {
pub(crate) 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(),
))
}
}

20
cyw43/src/util.rs Normal file
View File

@ -0,0 +1,20 @@
#![allow(unused)]
use core::slice;
pub(crate) 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) }
}
pub(crate) fn is_aligned(a: u32, x: u32) -> bool {
(a & (x - 1)) == 0
}
pub(crate) fn round_down(x: u32, a: u32) -> u32 {
x & !(a - 1)
}
pub(crate) fn round_up(x: u32, a: u32) -> u32 {
((x + a - 1) / a) * a
}

View File

@ -0,0 +1,26 @@
# Changelog for embassy-embedded-hal
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## Unreleased
## 0.3.0 - 2025-01-05
- The `std` feature has been removed
- Updated `embassy-time` to v0.4
## 0.2.0 - 2024-08-05
- Add Clone derive to flash Partition in embassy-embedded-hal
- Add support for all word sizes to async shared spi
- Add Copy and 'static constraint to Word type in SPI structs
- Improve flexibility by introducing SPI word size as a generic parameter
- Allow changing Spi/I2cDeviceWithConfig's config at runtime
- Impl `MultiwriteNorFlash` for `BlockingAsync`
## 0.1.0 - 2024-01-10
- First release

View File

@ -0,0 +1,42 @@
[package]
name = "embassy-embedded-hal"
version = "0.3.0"
edition = "2021"
license = "MIT OR Apache-2.0"
description = "Collection of utilities to use `embedded-hal` and `embedded-storage` traits with Embassy."
repository = "https://github.com/embassy-rs/embassy"
documentation = "https://docs.embassy.dev/embassy-embedded-hal"
categories = [
"embedded",
"no-std",
"asynchronous",
]
[package.metadata.embassy_docs]
src_base = "https://github.com/embassy-rs/embassy/blob/embassy-embedded-hal-v$VERSION/embassy-embedded-hal/src/"
src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-embedded-hal/src/"
target = "x86_64-unknown-linux-gnu"
[features]
time = ["dep:embassy-time"]
default = ["time"]
[dependencies]
embassy-hal-internal = { version = "0.2.0", path = "../embassy-hal-internal" }
embassy-futures = { version = "0.1.0", path = "../embassy-futures" }
embassy-sync = { version = "0.6.2", path = "../embassy-sync" }
embassy-time = { version = "0.4.0", path = "../embassy-time", optional = true }
embedded-hal-02 = { package = "embedded-hal", version = "0.2.6", features = [
"unproven",
] }
embedded-hal-1 = { package = "embedded-hal", version = "1.0" }
embedded-hal-async = { version = "1.0" }
embedded-storage = "0.3.1"
embedded-storage-async = { version = "0.4.1" }
nb = "1.0.0"
defmt = { version = "0.3", optional = true }
[dev-dependencies]
critical-section = { version = "1.1.1", features = ["std"] }
futures-test = "0.3.17"

View File

@ -0,0 +1,12 @@
# embassy-embedded-hal
Collection of utilities to use `embedded-hal` and `embedded-storage` traits with Embassy.
- Shared SPI and I2C buses, both blocking and async, with a `SetConfig` trait allowing changing bus configuration (e.g. frequency) between devices on the same bus.
- Async utilities
- Adapters to convert from blocking to (fake) async.
- Adapters to insert yields on trait operations.
- Flash utilities
- Split a flash memory into smaller partitions.
- Concatenate flash memories together.
- Simulated in-memory flash.

View File

@ -0,0 +1,149 @@
use embedded_hal_02::blocking;
/// Wrapper that implements async traits using blocking implementations.
///
/// This allows driver writers to depend on the async traits while still supporting embedded-hal peripheral implementations.
///
/// BlockingAsync will implement any async trait that maps to embedded-hal traits implemented for the wrapped driver.
///
/// Driver users are then free to choose which implementation that is available to them.
pub struct BlockingAsync<T> {
wrapped: T,
}
impl<T> BlockingAsync<T> {
/// Create a new instance of a wrapper for a given peripheral.
pub fn new(wrapped: T) -> Self {
Self { wrapped }
}
}
//
// I2C implementations
//
impl<T, E> embedded_hal_1::i2c::ErrorType for BlockingAsync<T>
where
E: embedded_hal_1::i2c::Error + 'static,
T: blocking::i2c::WriteRead<Error = E> + blocking::i2c::Read<Error = E> + blocking::i2c::Write<Error = E>,
{
type Error = E;
}
impl<T, E> embedded_hal_async::i2c::I2c for BlockingAsync<T>
where
E: embedded_hal_1::i2c::Error + 'static,
T: blocking::i2c::WriteRead<Error = E> + blocking::i2c::Read<Error = E> + blocking::i2c::Write<Error = E>,
{
async fn read(&mut self, address: u8, read: &mut [u8]) -> Result<(), Self::Error> {
self.wrapped.read(address, read)
}
async fn write(&mut self, address: u8, write: &[u8]) -> Result<(), Self::Error> {
self.wrapped.write(address, write)
}
async fn write_read(&mut self, address: u8, write: &[u8], read: &mut [u8]) -> Result<(), Self::Error> {
self.wrapped.write_read(address, write, read)
}
async fn transaction(
&mut self,
address: u8,
operations: &mut [embedded_hal_1::i2c::Operation<'_>],
) -> Result<(), Self::Error> {
let _ = address;
let _ = operations;
todo!()
}
}
//
// SPI implementatinos
//
impl<T, E> embedded_hal_async::spi::ErrorType for BlockingAsync<T>
where
E: embedded_hal_1::spi::Error,
T: blocking::spi::Transfer<u8, Error = E> + blocking::spi::Write<u8, Error = E>,
{
type Error = E;
}
impl<T, E> embedded_hal_async::spi::SpiBus<u8> for BlockingAsync<T>
where
E: embedded_hal_1::spi::Error + 'static,
T: blocking::spi::Transfer<u8, Error = E> + blocking::spi::Write<u8, Error = E>,
{
async fn flush(&mut self) -> Result<(), Self::Error> {
Ok(())
}
async fn write(&mut self, data: &[u8]) -> Result<(), Self::Error> {
self.wrapped.write(data)?;
Ok(())
}
async fn read(&mut self, data: &mut [u8]) -> Result<(), Self::Error> {
self.wrapped.transfer(data)?;
Ok(())
}
async fn transfer(&mut self, read: &mut [u8], write: &[u8]) -> Result<(), Self::Error> {
// Ensure we write the expected bytes
for i in 0..core::cmp::min(read.len(), write.len()) {
read[i] = write[i].clone();
}
self.wrapped.transfer(read)?;
Ok(())
}
async fn transfer_in_place(&mut self, data: &mut [u8]) -> Result<(), Self::Error> {
self.wrapped.transfer(data)?;
Ok(())
}
}
/// NOR flash wrapper
use embedded_storage::nor_flash::{ErrorType, MultiwriteNorFlash, NorFlash, ReadNorFlash};
use embedded_storage_async::nor_flash::{
MultiwriteNorFlash as AsyncMultiwriteNorFlash, NorFlash as AsyncNorFlash, ReadNorFlash as AsyncReadNorFlash,
};
impl<T> ErrorType for BlockingAsync<T>
where
T: ErrorType,
{
type Error = T::Error;
}
impl<T> AsyncNorFlash for BlockingAsync<T>
where
T: NorFlash,
{
const WRITE_SIZE: usize = <T as NorFlash>::WRITE_SIZE;
const ERASE_SIZE: usize = <T as NorFlash>::ERASE_SIZE;
async fn write(&mut self, offset: u32, data: &[u8]) -> Result<(), Self::Error> {
self.wrapped.write(offset, data)
}
async fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> {
self.wrapped.erase(from, to)
}
}
impl<T> AsyncReadNorFlash for BlockingAsync<T>
where
T: ReadNorFlash,
{
const READ_SIZE: usize = <T as ReadNorFlash>::READ_SIZE;
async fn read(&mut self, address: u32, data: &mut [u8]) -> Result<(), Self::Error> {
self.wrapped.read(address, data)
}
fn capacity(&self) -> usize {
self.wrapped.capacity()
}
}
impl<T> AsyncMultiwriteNorFlash for BlockingAsync<T> where T: MultiwriteNorFlash {}

View File

@ -0,0 +1,7 @@
//! Adapters between embedded-hal traits.
mod blocking_async;
mod yielding_async;
pub use blocking_async::BlockingAsync;
pub use yielding_async::YieldingAsync;

View File

@ -0,0 +1,169 @@
use embassy_futures::yield_now;
/// Wrapper that yields for each operation to the wrapped instance
///
/// This can be used in combination with BlockingAsync<T> to enforce yields
/// between long running blocking operations.
pub struct YieldingAsync<T> {
wrapped: T,
}
impl<T> YieldingAsync<T> {
/// Create a new instance of a wrapper that yields after each operation.
pub fn new(wrapped: T) -> Self {
Self { wrapped }
}
}
//
// I2C implementations
//
impl<T> embedded_hal_1::i2c::ErrorType for YieldingAsync<T>
where
T: embedded_hal_1::i2c::ErrorType,
{
type Error = T::Error;
}
impl<T> embedded_hal_async::i2c::I2c for YieldingAsync<T>
where
T: embedded_hal_async::i2c::I2c,
{
async fn read(&mut self, address: u8, read: &mut [u8]) -> Result<(), Self::Error> {
self.wrapped.read(address, read).await?;
yield_now().await;
Ok(())
}
async fn write(&mut self, address: u8, write: &[u8]) -> Result<(), Self::Error> {
self.wrapped.write(address, write).await?;
yield_now().await;
Ok(())
}
async fn write_read(&mut self, address: u8, write: &[u8], read: &mut [u8]) -> Result<(), Self::Error> {
self.wrapped.write_read(address, write, read).await?;
yield_now().await;
Ok(())
}
async fn transaction(
&mut self,
address: u8,
operations: &mut [embedded_hal_1::i2c::Operation<'_>],
) -> Result<(), Self::Error> {
self.wrapped.transaction(address, operations).await?;
yield_now().await;
Ok(())
}
}
//
// SPI implementations
//
impl<T> embedded_hal_async::spi::ErrorType for YieldingAsync<T>
where
T: embedded_hal_async::spi::ErrorType,
{
type Error = T::Error;
}
impl<T, Word: 'static + Copy> embedded_hal_async::spi::SpiBus<Word> for YieldingAsync<T>
where
T: embedded_hal_async::spi::SpiBus<Word>,
{
async fn flush(&mut self) -> Result<(), Self::Error> {
self.wrapped.flush().await?;
yield_now().await;
Ok(())
}
async fn write(&mut self, data: &[Word]) -> Result<(), Self::Error> {
self.wrapped.write(data).await?;
yield_now().await;
Ok(())
}
async fn read(&mut self, data: &mut [Word]) -> Result<(), Self::Error> {
self.wrapped.read(data).await?;
yield_now().await;
Ok(())
}
async fn transfer(&mut self, read: &mut [Word], write: &[Word]) -> Result<(), Self::Error> {
self.wrapped.transfer(read, write).await?;
yield_now().await;
Ok(())
}
async fn transfer_in_place(&mut self, words: &mut [Word]) -> Result<(), Self::Error> {
self.wrapped.transfer_in_place(words).await?;
yield_now().await;
Ok(())
}
}
///
/// NOR flash implementations
///
impl<T: embedded_storage::nor_flash::ErrorType> embedded_storage::nor_flash::ErrorType for YieldingAsync<T> {
type Error = T::Error;
}
impl<T: embedded_storage_async::nor_flash::ReadNorFlash> embedded_storage_async::nor_flash::ReadNorFlash
for YieldingAsync<T>
{
const READ_SIZE: usize = T::READ_SIZE;
async fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> {
self.wrapped.read(offset, bytes).await?;
Ok(())
}
fn capacity(&self) -> usize {
self.wrapped.capacity()
}
}
impl<T: embedded_storage_async::nor_flash::NorFlash> embedded_storage_async::nor_flash::NorFlash for YieldingAsync<T> {
const WRITE_SIZE: usize = T::WRITE_SIZE;
const ERASE_SIZE: usize = T::ERASE_SIZE;
async fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> {
self.wrapped.write(offset, bytes).await?;
yield_now().await;
Ok(())
}
async fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> {
// Yield between each actual erase
for from in (from..to).step_by(T::ERASE_SIZE) {
let to = core::cmp::min(from + T::ERASE_SIZE as u32, to);
self.wrapped.erase(from, to).await?;
yield_now().await;
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use embedded_storage_async::nor_flash::NorFlash;
use super::*;
use crate::flash::mem_flash::MemFlash;
#[futures_test::test]
async fn can_erase() {
let flash = MemFlash::<1024, 128, 4>::new(0x00);
let mut yielding = YieldingAsync::new(flash);
yielding.erase(0, 256).await.unwrap();
let flash = yielding.wrapped;
assert_eq!(2, flash.erases.len());
assert_eq!((0, 128), flash.erases[0]);
assert_eq!((128, 256), flash.erases[1]);
}
}

View File

@ -0,0 +1,225 @@
use embedded_storage::nor_flash::{ErrorType, NorFlash, NorFlashError, ReadNorFlash};
use embedded_storage_async::nor_flash::{NorFlash as AsyncNorFlash, ReadNorFlash as AsyncReadNorFlash};
/// Convenience helper for concatenating two consecutive flashes into one.
/// This is especially useful if used with "flash regions", where one may
/// want to concatenate multiple regions into one larger region.
pub struct ConcatFlash<First, Second>(First, Second);
impl<First, Second> ConcatFlash<First, Second> {
/// Create a new flash that concatenates two consecutive flashes.
pub fn new(first: First, second: Second) -> Self {
Self(first, second)
}
}
const fn get_read_size(first_read_size: usize, second_read_size: usize) -> usize {
if first_read_size != second_read_size {
panic!("The read size for the concatenated flashes must be the same");
}
first_read_size
}
const fn get_write_size(first_write_size: usize, second_write_size: usize) -> usize {
if first_write_size != second_write_size {
panic!("The write size for the concatenated flashes must be the same");
}
first_write_size
}
const fn get_max_erase_size(first_erase_size: usize, second_erase_size: usize) -> usize {
let max_erase_size = if first_erase_size > second_erase_size {
first_erase_size
} else {
second_erase_size
};
if max_erase_size % first_erase_size != 0 || max_erase_size % second_erase_size != 0 {
panic!("The erase sizes for the concatenated flashes must have have a gcd equal to the max erase size");
}
max_erase_size
}
impl<First, Second, E> ErrorType for ConcatFlash<First, Second>
where
First: ErrorType<Error = E>,
Second: ErrorType<Error = E>,
E: NorFlashError,
{
type Error = E;
}
impl<First, Second, E> ReadNorFlash for ConcatFlash<First, Second>
where
First: ReadNorFlash<Error = E>,
Second: ReadNorFlash<Error = E>,
E: NorFlashError,
{
const READ_SIZE: usize = get_read_size(First::READ_SIZE, Second::READ_SIZE);
fn read(&mut self, mut offset: u32, mut bytes: &mut [u8]) -> Result<(), E> {
if offset < self.0.capacity() as u32 {
let len = core::cmp::min(self.0.capacity() - offset as usize, bytes.len());
self.0.read(offset, &mut bytes[..len])?;
offset += len as u32;
bytes = &mut bytes[len..];
}
if !bytes.is_empty() {
self.1.read(offset - self.0.capacity() as u32, bytes)?;
}
Ok(())
}
fn capacity(&self) -> usize {
self.0.capacity() + self.1.capacity()
}
}
impl<First, Second, E> NorFlash for ConcatFlash<First, Second>
where
First: NorFlash<Error = E>,
Second: NorFlash<Error = E>,
E: NorFlashError,
{
const WRITE_SIZE: usize = get_write_size(First::WRITE_SIZE, Second::WRITE_SIZE);
const ERASE_SIZE: usize = get_max_erase_size(First::ERASE_SIZE, Second::ERASE_SIZE);
fn write(&mut self, mut offset: u32, mut bytes: &[u8]) -> Result<(), E> {
if offset < self.0.capacity() as u32 {
let len = core::cmp::min(self.0.capacity() - offset as usize, bytes.len());
self.0.write(offset, &bytes[..len])?;
offset += len as u32;
bytes = &bytes[len..];
}
if !bytes.is_empty() {
self.1.write(offset - self.0.capacity() as u32, bytes)?;
}
Ok(())
}
fn erase(&mut self, mut from: u32, to: u32) -> Result<(), E> {
if from < self.0.capacity() as u32 {
let to = core::cmp::min(self.0.capacity() as u32, to);
self.0.erase(from, to)?;
from = self.0.capacity() as u32;
}
if from < to {
self.1
.erase(from - self.0.capacity() as u32, to - self.0.capacity() as u32)?;
}
Ok(())
}
}
impl<First, Second, E> AsyncReadNorFlash for ConcatFlash<First, Second>
where
First: AsyncReadNorFlash<Error = E>,
Second: AsyncReadNorFlash<Error = E>,
E: NorFlashError,
{
const READ_SIZE: usize = get_read_size(First::READ_SIZE, Second::READ_SIZE);
async fn read(&mut self, mut offset: u32, mut bytes: &mut [u8]) -> Result<(), E> {
if offset < self.0.capacity() as u32 {
let len = core::cmp::min(self.0.capacity() - offset as usize, bytes.len());
self.0.read(offset, &mut bytes[..len]).await?;
offset += len as u32;
bytes = &mut bytes[len..];
}
if !bytes.is_empty() {
self.1.read(offset - self.0.capacity() as u32, bytes).await?;
}
Ok(())
}
fn capacity(&self) -> usize {
self.0.capacity() + self.1.capacity()
}
}
impl<First, Second, E> AsyncNorFlash for ConcatFlash<First, Second>
where
First: AsyncNorFlash<Error = E>,
Second: AsyncNorFlash<Error = E>,
E: NorFlashError,
{
const WRITE_SIZE: usize = get_write_size(First::WRITE_SIZE, Second::WRITE_SIZE);
const ERASE_SIZE: usize = get_max_erase_size(First::ERASE_SIZE, Second::ERASE_SIZE);
async fn write(&mut self, mut offset: u32, mut bytes: &[u8]) -> Result<(), E> {
if offset < self.0.capacity() as u32 {
let len = core::cmp::min(self.0.capacity() - offset as usize, bytes.len());
self.0.write(offset, &bytes[..len]).await?;
offset += len as u32;
bytes = &bytes[len..];
}
if !bytes.is_empty() {
self.1.write(offset - self.0.capacity() as u32, bytes).await?;
}
Ok(())
}
async fn erase(&mut self, mut from: u32, to: u32) -> Result<(), E> {
if from < self.0.capacity() as u32 {
let to = core::cmp::min(self.0.capacity() as u32, to);
self.0.erase(from, to).await?;
from = self.0.capacity() as u32;
}
if from < to {
self.1
.erase(from - self.0.capacity() as u32, to - self.0.capacity() as u32)
.await?;
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use embedded_storage::nor_flash::{NorFlash, ReadNorFlash};
use super::ConcatFlash;
use crate::flash::mem_flash::MemFlash;
#[test]
fn can_write_and_read_across_flashes() {
let first = MemFlash::<64, 16, 4>::default();
let second = MemFlash::<64, 64, 4>::default();
let mut f = ConcatFlash::new(first, second);
f.write(60, &[0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88]).unwrap();
assert_eq!(&[0x11, 0x22, 0x33, 0x44], &f.0.mem[60..]);
assert_eq!(&[0x55, 0x66, 0x77, 0x88], &f.1.mem[0..4]);
let mut read_buf = [0; 8];
f.read(60, &mut read_buf).unwrap();
assert_eq!(&[0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88], &read_buf);
}
#[test]
fn can_erase_across_flashes() {
let first = MemFlash::<128, 16, 4>::new(0x00);
let second = MemFlash::<128, 64, 4>::new(0x00);
let mut f = ConcatFlash::new(first, second);
f.erase(64, 192).unwrap();
assert_eq!(&[0x00; 64], &f.0.mem[0..64]);
assert_eq!(&[0xff; 64], &f.0.mem[64..128]);
assert_eq!(&[0xff; 64], &f.1.mem[0..64]);
assert_eq!(&[0x00; 64], &f.1.mem[64..128]);
}
}

View File

@ -0,0 +1,125 @@
use alloc::vec::Vec;
use embedded_storage::nor_flash::{ErrorType, NorFlash, ReadNorFlash};
use embedded_storage_async::nor_flash::{NorFlash as AsyncNorFlash, ReadNorFlash as AsyncReadNorFlash};
extern crate alloc;
pub(crate) struct MemFlash<const SIZE: usize, const ERASE_SIZE: usize, const WRITE_SIZE: usize> {
pub mem: [u8; SIZE],
pub writes: Vec<(u32, usize)>,
pub erases: Vec<(u32, u32)>,
}
impl<const SIZE: usize, const ERASE_SIZE: usize, const WRITE_SIZE: usize> MemFlash<SIZE, ERASE_SIZE, WRITE_SIZE> {
#[allow(unused)]
pub const fn new(fill: u8) -> Self {
Self {
mem: [fill; SIZE],
writes: Vec::new(),
erases: Vec::new(),
}
}
fn read(&mut self, offset: u32, bytes: &mut [u8]) {
let len = bytes.len();
bytes.copy_from_slice(&self.mem[offset as usize..offset as usize + len]);
}
fn write(&mut self, offset: u32, bytes: &[u8]) {
self.writes.push((offset, bytes.len()));
let offset = offset as usize;
assert_eq!(0, bytes.len() % WRITE_SIZE);
assert_eq!(0, offset % WRITE_SIZE);
assert!(offset + bytes.len() <= SIZE);
self.mem[offset..offset + bytes.len()].copy_from_slice(bytes);
}
fn erase(&mut self, from: u32, to: u32) {
self.erases.push((from, to));
let from = from as usize;
let to = to as usize;
assert_eq!(0, from % ERASE_SIZE);
assert_eq!(0, to % ERASE_SIZE);
self.mem[from..to].fill(0xff);
}
}
impl<const SIZE: usize, const ERASE_SIZE: usize, const WRITE_SIZE: usize> Default
for MemFlash<SIZE, ERASE_SIZE, WRITE_SIZE>
{
fn default() -> Self {
Self::new(0xff)
}
}
impl<const SIZE: usize, const ERASE_SIZE: usize, const WRITE_SIZE: usize> ErrorType
for MemFlash<SIZE, ERASE_SIZE, WRITE_SIZE>
{
type Error = core::convert::Infallible;
}
impl<const SIZE: usize, const ERASE_SIZE: usize, const WRITE_SIZE: usize> ReadNorFlash
for MemFlash<SIZE, ERASE_SIZE, WRITE_SIZE>
{
const READ_SIZE: usize = 1;
fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> {
self.read(offset, bytes);
Ok(())
}
fn capacity(&self) -> usize {
SIZE
}
}
impl<const SIZE: usize, const ERASE_SIZE: usize, const WRITE_SIZE: usize> NorFlash
for MemFlash<SIZE, ERASE_SIZE, WRITE_SIZE>
{
const WRITE_SIZE: usize = WRITE_SIZE;
const ERASE_SIZE: usize = ERASE_SIZE;
fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> {
self.write(offset, bytes);
Ok(())
}
fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> {
self.erase(from, to);
Ok(())
}
}
impl<const SIZE: usize, const ERASE_SIZE: usize, const WRITE_SIZE: usize> AsyncReadNorFlash
for MemFlash<SIZE, ERASE_SIZE, WRITE_SIZE>
{
const READ_SIZE: usize = 1;
async fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> {
self.read(offset, bytes);
Ok(())
}
fn capacity(&self) -> usize {
SIZE
}
}
impl<const SIZE: usize, const ERASE_SIZE: usize, const WRITE_SIZE: usize> AsyncNorFlash
for MemFlash<SIZE, ERASE_SIZE, WRITE_SIZE>
{
const WRITE_SIZE: usize = WRITE_SIZE;
const ERASE_SIZE: usize = ERASE_SIZE;
async fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> {
self.write(offset, bytes);
Ok(())
}
async fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> {
self.erase(from, to);
Ok(())
}
}

View File

@ -0,0 +1,8 @@
//! Utilities related to flash.
mod concat_flash;
#[cfg(test)]
pub(crate) mod mem_flash;
pub mod partition;
pub use concat_flash::ConcatFlash;

View File

@ -0,0 +1,149 @@
use embassy_sync::blocking_mutex::raw::RawMutex;
use embassy_sync::mutex::Mutex;
use embedded_storage::nor_flash::ErrorType;
use embedded_storage_async::nor_flash::{NorFlash, ReadNorFlash};
use super::Error;
/// A logical partition of an underlying shared flash
///
/// A partition holds an offset and a size of the flash,
/// and is restricted to operate with that range.
/// There is no guarantee that muliple partitions on the same flash
/// operate on mutually exclusive ranges - such a separation is up to
/// the user to guarantee.
pub struct Partition<'a, M: RawMutex, T: NorFlash> {
flash: &'a Mutex<M, T>,
offset: u32,
size: u32,
}
impl<'a, M: RawMutex, T: NorFlash> Clone for Partition<'a, M, T> {
fn clone(&self) -> Self {
Self {
flash: self.flash,
offset: self.offset,
size: self.size,
}
}
}
impl<'a, M: RawMutex, T: NorFlash> Partition<'a, M, T> {
/// Create a new partition
pub const fn new(flash: &'a Mutex<M, T>, offset: u32, size: u32) -> Self {
if offset % T::READ_SIZE as u32 != 0 || offset % T::WRITE_SIZE as u32 != 0 || offset % T::ERASE_SIZE as u32 != 0
{
panic!("Partition offset must be a multiple of read, write and erase size");
}
if size % T::READ_SIZE as u32 != 0 || size % T::WRITE_SIZE as u32 != 0 || size % T::ERASE_SIZE as u32 != 0 {
panic!("Partition size must be a multiple of read, write and erase size");
}
Self { flash, offset, size }
}
/// Get the partition offset within the flash
pub const fn offset(&self) -> u32 {
self.offset
}
/// Get the partition size
pub const fn size(&self) -> u32 {
self.size
}
}
impl<M: RawMutex, T: NorFlash> ErrorType for Partition<'_, M, T> {
type Error = Error<T::Error>;
}
impl<M: RawMutex, T: NorFlash> ReadNorFlash for Partition<'_, M, T> {
const READ_SIZE: usize = T::READ_SIZE;
async fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> {
if offset + bytes.len() as u32 > self.size {
return Err(Error::OutOfBounds);
}
let mut flash = self.flash.lock().await;
flash.read(self.offset + offset, bytes).await.map_err(Error::Flash)
}
fn capacity(&self) -> usize {
self.size as usize
}
}
impl<M: RawMutex, T: NorFlash> NorFlash for Partition<'_, M, T> {
const WRITE_SIZE: usize = T::WRITE_SIZE;
const ERASE_SIZE: usize = T::ERASE_SIZE;
async fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> {
if offset + bytes.len() as u32 > self.size {
return Err(Error::OutOfBounds);
}
let mut flash = self.flash.lock().await;
flash.write(self.offset + offset, bytes).await.map_err(Error::Flash)
}
async fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> {
if to > self.size {
return Err(Error::OutOfBounds);
}
let mut flash = self.flash.lock().await;
flash
.erase(self.offset + from, self.offset + to)
.await
.map_err(Error::Flash)
}
}
#[cfg(test)]
mod tests {
use embassy_sync::blocking_mutex::raw::NoopRawMutex;
use super::*;
use crate::flash::mem_flash::MemFlash;
#[futures_test::test]
async fn can_read() {
let mut flash = MemFlash::<1024, 128, 4>::default();
flash.mem[132..132 + 8].fill(0xAA);
let flash = Mutex::<NoopRawMutex, _>::new(flash);
let mut partition = Partition::new(&flash, 128, 256);
let mut read_buf = [0; 8];
partition.read(4, &mut read_buf).await.unwrap();
assert!(read_buf.iter().position(|&x| x != 0xAA).is_none());
}
#[futures_test::test]
async fn can_write() {
let flash = MemFlash::<1024, 128, 4>::default();
let flash = Mutex::<NoopRawMutex, _>::new(flash);
let mut partition = Partition::new(&flash, 128, 256);
let write_buf = [0xAA; 8];
partition.write(4, &write_buf).await.unwrap();
let flash = flash.try_lock().unwrap();
assert!(flash.mem[132..132 + 8].iter().position(|&x| x != 0xAA).is_none());
}
#[futures_test::test]
async fn can_erase() {
let flash = MemFlash::<1024, 128, 4>::new(0x00);
let flash = Mutex::<NoopRawMutex, _>::new(flash);
let mut partition = Partition::new(&flash, 128, 256);
partition.erase(0, 128).await.unwrap();
let flash = flash.try_lock().unwrap();
assert!(flash.mem[128..256].iter().position(|&x| x != 0xFF).is_none());
}
}

View File

@ -0,0 +1,159 @@
use core::cell::RefCell;
use embassy_sync::blocking_mutex::raw::RawMutex;
use embassy_sync::blocking_mutex::Mutex;
use embedded_storage::nor_flash::{ErrorType, NorFlash, ReadNorFlash};
use super::Error;
/// A logical partition of an underlying shared flash
///
/// A partition holds an offset and a size of the flash,
/// and is restricted to operate with that range.
/// There is no guarantee that muliple partitions on the same flash
/// operate on mutually exclusive ranges - such a separation is up to
/// the user to guarantee.
pub struct BlockingPartition<'a, M: RawMutex, T: NorFlash> {
flash: &'a Mutex<M, RefCell<T>>,
offset: u32,
size: u32,
}
impl<'a, M: RawMutex, T: NorFlash> Clone for BlockingPartition<'a, M, T> {
fn clone(&self) -> Self {
Self {
flash: self.flash,
offset: self.offset,
size: self.size,
}
}
}
impl<'a, M: RawMutex, T: NorFlash> BlockingPartition<'a, M, T> {
/// Create a new partition
pub const fn new(flash: &'a Mutex<M, RefCell<T>>, offset: u32, size: u32) -> Self {
if offset % T::READ_SIZE as u32 != 0 || offset % T::WRITE_SIZE as u32 != 0 || offset % T::ERASE_SIZE as u32 != 0
{
panic!("Partition offset must be a multiple of read, write and erase size");
}
if size % T::READ_SIZE as u32 != 0 || size % T::WRITE_SIZE as u32 != 0 || size % T::ERASE_SIZE as u32 != 0 {
panic!("Partition size must be a multiple of read, write and erase size");
}
Self { flash, offset, size }
}
/// Get the partition offset within the flash
pub const fn offset(&self) -> u32 {
self.offset
}
/// Get the partition size
pub const fn size(&self) -> u32 {
self.size
}
}
impl<M: RawMutex, T: NorFlash> ErrorType for BlockingPartition<'_, M, T> {
type Error = Error<T::Error>;
}
impl<M: RawMutex, T: NorFlash> ReadNorFlash for BlockingPartition<'_, M, T> {
const READ_SIZE: usize = T::READ_SIZE;
fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> {
if offset + bytes.len() as u32 > self.size {
return Err(Error::OutOfBounds);
}
self.flash.lock(|flash| {
flash
.borrow_mut()
.read(self.offset + offset, bytes)
.map_err(Error::Flash)
})
}
fn capacity(&self) -> usize {
self.size as usize
}
}
impl<M: RawMutex, T: NorFlash> NorFlash for BlockingPartition<'_, M, T> {
const WRITE_SIZE: usize = T::WRITE_SIZE;
const ERASE_SIZE: usize = T::ERASE_SIZE;
fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> {
if offset + bytes.len() as u32 > self.size {
return Err(Error::OutOfBounds);
}
self.flash.lock(|flash| {
flash
.borrow_mut()
.write(self.offset + offset, bytes)
.map_err(Error::Flash)
})
}
fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> {
if to > self.size {
return Err(Error::OutOfBounds);
}
self.flash.lock(|flash| {
flash
.borrow_mut()
.erase(self.offset + from, self.offset + to)
.map_err(Error::Flash)
})
}
}
#[cfg(test)]
mod tests {
use embassy_sync::blocking_mutex::raw::NoopRawMutex;
use super::*;
use crate::flash::mem_flash::MemFlash;
#[test]
fn can_read() {
let mut flash = MemFlash::<1024, 128, 4>::default();
flash.mem[132..132 + 8].fill(0xAA);
let flash = Mutex::<NoopRawMutex, _>::new(RefCell::new(flash));
let mut partition = BlockingPartition::new(&flash, 128, 256);
let mut read_buf = [0; 8];
partition.read(4, &mut read_buf).unwrap();
assert!(read_buf.iter().position(|&x| x != 0xAA).is_none());
}
#[test]
fn can_write() {
let flash = MemFlash::<1024, 128, 4>::default();
let flash = Mutex::<NoopRawMutex, _>::new(RefCell::new(flash));
let mut partition = BlockingPartition::new(&flash, 128, 256);
let write_buf = [0xAA; 8];
partition.write(4, &write_buf).unwrap();
let flash = flash.into_inner().take();
assert!(flash.mem[132..132 + 8].iter().position(|&x| x != 0xAA).is_none());
}
#[test]
fn can_erase() {
let flash = MemFlash::<1024, 128, 4>::new(0x00);
let flash = Mutex::<NoopRawMutex, _>::new(RefCell::new(flash));
let mut partition = BlockingPartition::new(&flash, 128, 256);
partition.erase(0, 128).unwrap();
let flash = flash.into_inner().take();
assert!(flash.mem[128..256].iter().position(|&x| x != 0xFF).is_none());
}
}

View File

@ -0,0 +1,28 @@
//! Flash Partition utilities
use embedded_storage::nor_flash::{NorFlashError, NorFlashErrorKind};
mod asynch;
mod blocking;
pub use asynch::Partition;
pub use blocking::BlockingPartition;
/// Partition error
#[derive(Debug, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum Error<T> {
/// The requested flash area is outside the partition
OutOfBounds,
/// Underlying flash error
Flash(T),
}
impl<T: NorFlashError> NorFlashError for Error<T> {
fn kind(&self) -> NorFlashErrorKind {
match self {
Error::OutOfBounds => NorFlashErrorKind::OutOfBounds,
Error::Flash(f) => f.kind(),
}
}
}

View File

@ -0,0 +1,39 @@
#![no_std]
#![allow(async_fn_in_trait)]
#![warn(missing_docs)]
#![doc = include_str!("../README.md")]
pub mod adapter;
pub mod flash;
pub mod shared_bus;
/// Set the configuration of a peripheral driver.
///
/// This trait is intended to be implemented by peripheral drivers such as SPI
/// and I2C. It allows changing the configuration at runtime.
///
/// The exact type of the "configuration" is defined by each individual driver, since different
/// drivers support different options. Therefore it is defined as an associated type.
///
/// For example, it is used by [`SpiDeviceWithConfig`](crate::shared_bus::asynch::spi::SpiDeviceWithConfig) and
/// [`I2cDeviceWithConfig`](crate::shared_bus::asynch::i2c::I2cDeviceWithConfig) to allow different
/// devices on the same bus to use different communication settings.
pub trait SetConfig {
/// The configuration type used by this driver.
type Config;
/// The error type that can occur if `set_config` fails.
type ConfigError;
/// Set the configuration of the driver.
fn set_config(&mut self, config: &Self::Config) -> Result<(), Self::ConfigError>;
}
/// Get the configuration of a peripheral driver.
pub trait GetConfig {
/// The configuration type used by this driver.
type Config;
/// Get the configuration of the driver.
fn get_config(&self) -> Self::Config;
}

View File

@ -0,0 +1,166 @@
//! Asynchronous shared I2C bus
//!
//! # Example (nrf52)
//!
//! ```rust,ignore
//! use embassy_embedded_hal::shared_bus::asynch::i2c::I2cDevice;
//! use embassy_sync::mutex::Mutex;
//! use embassy_sync::blocking_mutex::raw::NoopRawMutex;
//!
//! static I2C_BUS: StaticCell<Mutex<NoopRawMutex, Twim<TWISPI0>>> = StaticCell::new();
//! let config = twim::Config::default();
//! let i2c = Twim::new(p.TWISPI0, Irqs, p.P0_03, p.P0_04, config);
//! let i2c_bus = Mutex::new(i2c);
//! let i2c_bus = I2C_BUS.init(i2c_bus);
//!
//! // Device 1, using embedded-hal-async compatible driver for QMC5883L compass
//! let i2c_dev1 = I2cDevice::new(i2c_bus);
//! let compass = QMC5883L::new(i2c_dev1).await.unwrap();
//!
//! // Device 2, using embedded-hal-async compatible driver for Mpu6050 accelerometer
//! let i2c_dev2 = I2cDevice::new(i2c_bus);
//! let mpu = Mpu6050::new(i2c_dev2);
//! ```
use embassy_sync::blocking_mutex::raw::RawMutex;
use embassy_sync::mutex::Mutex;
use embedded_hal_async::i2c;
use crate::shared_bus::I2cDeviceError;
use crate::SetConfig;
/// I2C device on a shared bus.
pub struct I2cDevice<'a, M: RawMutex, BUS> {
bus: &'a Mutex<M, BUS>,
}
impl<'a, M: RawMutex, BUS> I2cDevice<'a, M, BUS> {
/// Create a new `I2cDevice`.
pub fn new(bus: &'a Mutex<M, BUS>) -> Self {
Self { bus }
}
}
impl<'a, M: RawMutex, BUS> i2c::ErrorType for I2cDevice<'a, M, BUS>
where
BUS: i2c::ErrorType,
{
type Error = I2cDeviceError<BUS::Error>;
}
impl<M, BUS> i2c::I2c for I2cDevice<'_, M, BUS>
where
M: RawMutex + 'static,
BUS: i2c::I2c + 'static,
{
async fn read(&mut self, address: u8, read: &mut [u8]) -> Result<(), I2cDeviceError<BUS::Error>> {
let mut bus = self.bus.lock().await;
bus.read(address, read).await.map_err(I2cDeviceError::I2c)?;
Ok(())
}
async fn write(&mut self, address: u8, write: &[u8]) -> Result<(), I2cDeviceError<BUS::Error>> {
let mut bus = self.bus.lock().await;
bus.write(address, write).await.map_err(I2cDeviceError::I2c)?;
Ok(())
}
async fn write_read(
&mut self,
address: u8,
write: &[u8],
read: &mut [u8],
) -> Result<(), I2cDeviceError<BUS::Error>> {
let mut bus = self.bus.lock().await;
bus.write_read(address, write, read)
.await
.map_err(I2cDeviceError::I2c)?;
Ok(())
}
async fn transaction(
&mut self,
address: u8,
operations: &mut [embedded_hal_async::i2c::Operation<'_>],
) -> Result<(), I2cDeviceError<BUS::Error>> {
let mut bus = self.bus.lock().await;
bus.transaction(address, operations)
.await
.map_err(I2cDeviceError::I2c)?;
Ok(())
}
}
/// I2C device on a shared bus, with its own configuration.
///
/// This is like [`I2cDevice`], with an additional bus configuration that's applied
/// to the bus before each use using [`SetConfig`]. This allows different
/// devices on the same bus to use different communication settings.
pub struct I2cDeviceWithConfig<'a, M: RawMutex, BUS: SetConfig> {
bus: &'a Mutex<M, BUS>,
config: BUS::Config,
}
impl<'a, M: RawMutex, BUS: SetConfig> I2cDeviceWithConfig<'a, M, BUS> {
/// Create a new `I2cDeviceWithConfig`.
pub fn new(bus: &'a Mutex<M, BUS>, config: BUS::Config) -> Self {
Self { bus, config }
}
/// Change the device's config at runtime
pub fn set_config(&mut self, config: BUS::Config) {
self.config = config;
}
}
impl<'a, M, BUS> i2c::ErrorType for I2cDeviceWithConfig<'a, M, BUS>
where
BUS: i2c::ErrorType,
M: RawMutex,
BUS: SetConfig,
{
type Error = I2cDeviceError<BUS::Error>;
}
impl<M, BUS> i2c::I2c for I2cDeviceWithConfig<'_, M, BUS>
where
M: RawMutex + 'static,
BUS: i2c::I2c + SetConfig + 'static,
{
async fn read(&mut self, address: u8, buffer: &mut [u8]) -> Result<(), I2cDeviceError<BUS::Error>> {
let mut bus = self.bus.lock().await;
bus.set_config(&self.config).map_err(|_| I2cDeviceError::Config)?;
bus.read(address, buffer).await.map_err(I2cDeviceError::I2c)?;
Ok(())
}
async fn write(&mut self, address: u8, bytes: &[u8]) -> Result<(), I2cDeviceError<BUS::Error>> {
let mut bus = self.bus.lock().await;
bus.set_config(&self.config).map_err(|_| I2cDeviceError::Config)?;
bus.write(address, bytes).await.map_err(I2cDeviceError::I2c)?;
Ok(())
}
async fn write_read(
&mut self,
address: u8,
wr_buffer: &[u8],
rd_buffer: &mut [u8],
) -> Result<(), I2cDeviceError<BUS::Error>> {
let mut bus = self.bus.lock().await;
bus.set_config(&self.config).map_err(|_| I2cDeviceError::Config)?;
bus.write_read(address, wr_buffer, rd_buffer)
.await
.map_err(I2cDeviceError::I2c)?;
Ok(())
}
async fn transaction(&mut self, address: u8, operations: &mut [i2c::Operation<'_>]) -> Result<(), Self::Error> {
let mut bus = self.bus.lock().await;
bus.set_config(&self.config).map_err(|_| I2cDeviceError::Config)?;
bus.transaction(address, operations)
.await
.map_err(I2cDeviceError::I2c)?;
Ok(())
}
}

View File

@ -0,0 +1,3 @@
//! Asynchronous shared bus implementations for embedded-hal-async
pub mod i2c;
pub mod spi;

View File

@ -0,0 +1,212 @@
//! Asynchronous shared SPI bus
//!
//! # Example (nrf52)
//!
//! ```rust,ignore
//! use embassy_embedded_hal::shared_bus::spi::SpiDevice;
//! use embassy_sync::mutex::Mutex;
//! use embassy_sync::blocking_mutex::raw::NoopRawMutex;
//!
//! static SPI_BUS: StaticCell<Mutex<NoopRawMutex, spim::Spim<SPI3>>> = StaticCell::new();
//! let mut config = spim::Config::default();
//! config.frequency = spim::Frequency::M32;
//! let spi = spim::Spim::new_txonly(p.SPI3, Irqs, p.P0_15, p.P0_18, config);
//! let spi_bus = Mutex::new(spi);
//! let spi_bus = SPI_BUS.init(spi_bus);
//!
//! // Device 1, using embedded-hal-async compatible driver for ST7735 LCD display
//! let cs_pin1 = Output::new(p.P0_24, Level::Low, OutputDrive::Standard);
//! let spi_dev1 = SpiDevice::new(spi_bus, cs_pin1);
//! let display1 = ST7735::new(spi_dev1, dc1, rst1, Default::default(), 160, 128);
//!
//! // Device 2
//! let cs_pin2 = Output::new(p.P0_24, Level::Low, OutputDrive::Standard);
//! let spi_dev2 = SpiDevice::new(spi_bus, cs_pin2);
//! let display2 = ST7735::new(spi_dev2, dc2, rst2, Default::default(), 160, 128);
//! ```
use embassy_hal_internal::drop::OnDrop;
use embassy_sync::blocking_mutex::raw::RawMutex;
use embassy_sync::mutex::Mutex;
use embedded_hal_1::digital::OutputPin;
use embedded_hal_1::spi::Operation;
use embedded_hal_async::spi;
use crate::shared_bus::SpiDeviceError;
use crate::SetConfig;
/// SPI device on a shared bus.
pub struct SpiDevice<'a, M: RawMutex, BUS, CS> {
bus: &'a Mutex<M, BUS>,
cs: CS,
}
impl<'a, M: RawMutex, BUS, CS> SpiDevice<'a, M, BUS, CS> {
/// Create a new `SpiDevice`.
pub fn new(bus: &'a Mutex<M, BUS>, cs: CS) -> Self {
Self { bus, cs }
}
}
impl<'a, M: RawMutex, BUS, CS> spi::ErrorType for SpiDevice<'a, M, BUS, CS>
where
BUS: spi::ErrorType,
CS: OutputPin,
{
type Error = SpiDeviceError<BUS::Error, CS::Error>;
}
impl<M, BUS, CS, Word> spi::SpiDevice<Word> for SpiDevice<'_, M, BUS, CS>
where
M: RawMutex,
BUS: spi::SpiBus<Word>,
CS: OutputPin,
Word: Copy + 'static,
{
async fn transaction(&mut self, operations: &mut [spi::Operation<'_, Word>]) -> Result<(), Self::Error> {
if cfg!(not(feature = "time")) && operations.iter().any(|op| matches!(op, Operation::DelayNs(_))) {
return Err(SpiDeviceError::DelayNotSupported);
}
let mut bus = self.bus.lock().await;
self.cs.set_low().map_err(SpiDeviceError::Cs)?;
let cs_drop = OnDrop::new(|| {
// This drop guard deasserts CS pin if the async operation is cancelled.
// Errors are ignored in this drop handler, as there's nothing we can do about them.
// If the async operation is completed without cancellation, this handler will not
// be run, and the CS pin will be deasserted with proper error handling.
let _ = self.cs.set_high();
});
let op_res = 'ops: {
for op in operations {
let res = match op {
Operation::Read(buf) => bus.read(buf).await,
Operation::Write(buf) => bus.write(buf).await,
Operation::Transfer(read, write) => bus.transfer(read, write).await,
Operation::TransferInPlace(buf) => bus.transfer_in_place(buf).await,
#[cfg(not(feature = "time"))]
Operation::DelayNs(_) => unreachable!(),
#[cfg(feature = "time")]
Operation::DelayNs(ns) => match bus.flush().await {
Err(e) => Err(e),
Ok(()) => {
embassy_time::Timer::after_nanos(*ns as _).await;
Ok(())
}
},
};
if let Err(e) = res {
break 'ops Err(e);
}
}
Ok(())
};
// On failure, it's important to still flush and deassert CS.
let flush_res = bus.flush().await;
// Now that all the async operations are done, we defuse the CS guard,
// and manually set the CS pin low (to better handle the possible errors).
cs_drop.defuse();
let cs_res = self.cs.set_high();
let op_res = op_res.map_err(SpiDeviceError::Spi)?;
flush_res.map_err(SpiDeviceError::Spi)?;
cs_res.map_err(SpiDeviceError::Cs)?;
Ok(op_res)
}
}
/// SPI device on a shared bus, with its own configuration.
///
/// This is like [`SpiDevice`], with an additional bus configuration that's applied
/// to the bus before each use using [`SetConfig`]. This allows different
/// devices on the same bus to use different communication settings.
pub struct SpiDeviceWithConfig<'a, M: RawMutex, BUS: SetConfig, CS> {
bus: &'a Mutex<M, BUS>,
cs: CS,
config: BUS::Config,
}
impl<'a, M: RawMutex, BUS: SetConfig, CS> SpiDeviceWithConfig<'a, M, BUS, CS> {
/// Create a new `SpiDeviceWithConfig`.
pub fn new(bus: &'a Mutex<M, BUS>, cs: CS, config: BUS::Config) -> Self {
Self { bus, cs, config }
}
/// Change the device's config at runtime
pub fn set_config(&mut self, config: BUS::Config) {
self.config = config;
}
}
impl<'a, M, BUS, CS> spi::ErrorType for SpiDeviceWithConfig<'a, M, BUS, CS>
where
BUS: spi::ErrorType + SetConfig,
CS: OutputPin,
M: RawMutex,
{
type Error = SpiDeviceError<BUS::Error, CS::Error>;
}
impl<M, BUS, CS, Word> spi::SpiDevice<Word> for SpiDeviceWithConfig<'_, M, BUS, CS>
where
M: RawMutex,
BUS: spi::SpiBus<Word> + SetConfig,
CS: OutputPin,
Word: Copy + 'static,
{
async fn transaction(&mut self, operations: &mut [spi::Operation<'_, Word>]) -> Result<(), Self::Error> {
if cfg!(not(feature = "time")) && operations.iter().any(|op| matches!(op, Operation::DelayNs(_))) {
return Err(SpiDeviceError::DelayNotSupported);
}
let mut bus = self.bus.lock().await;
bus.set_config(&self.config).map_err(|_| SpiDeviceError::Config)?;
self.cs.set_low().map_err(SpiDeviceError::Cs)?;
let cs_drop = OnDrop::new(|| {
// Please see comment in SpiDevice for an explanation of this drop handler.
let _ = self.cs.set_high();
});
let op_res = 'ops: {
for op in operations {
let res = match op {
Operation::Read(buf) => bus.read(buf).await,
Operation::Write(buf) => bus.write(buf).await,
Operation::Transfer(read, write) => bus.transfer(read, write).await,
Operation::TransferInPlace(buf) => bus.transfer_in_place(buf).await,
#[cfg(not(feature = "time"))]
Operation::DelayNs(_) => unreachable!(),
#[cfg(feature = "time")]
Operation::DelayNs(ns) => match bus.flush().await {
Err(e) => Err(e),
Ok(()) => {
embassy_time::Timer::after_nanos(*ns as _).await;
Ok(())
}
},
};
if let Err(e) = res {
break 'ops Err(e);
}
}
Ok(())
};
// On failure, it's important to still flush and deassert CS.
let flush_res = bus.flush().await;
cs_drop.defuse();
let cs_res = self.cs.set_high();
let op_res = op_res.map_err(SpiDeviceError::Spi)?;
flush_res.map_err(SpiDeviceError::Spi)?;
cs_res.map_err(SpiDeviceError::Cs)?;
Ok(op_res)
}
}

View File

@ -0,0 +1,187 @@
//! Blocking shared I2C bus
//!
//! # Example (nrf52)
//!
//! ```rust,ignore
//! use embassy_embedded_hal::shared_bus::blocking::i2c::I2cDevice;
//! use embassy_sync::blocking_mutex::{NoopMutex, raw::NoopRawMutex};
//!
//! static I2C_BUS: StaticCell<NoopMutex<RefCell<Twim<TWISPI0>>>> = StaticCell::new();
//! let i2c = Twim::new(p.TWISPI0, Irqs, p.P0_03, p.P0_04, Config::default());
//! let i2c_bus = NoopMutex::new(RefCell::new(i2c));
//! let i2c_bus = I2C_BUS.init(i2c_bus);
//!
//! let i2c_dev1 = I2cDevice::new(i2c_bus);
//! let mpu = Mpu6050::new(i2c_dev1);
//! ```
use core::cell::RefCell;
use embassy_sync::blocking_mutex::raw::RawMutex;
use embassy_sync::blocking_mutex::Mutex;
use embedded_hal_1::i2c::{ErrorType, I2c, Operation};
use crate::shared_bus::I2cDeviceError;
use crate::SetConfig;
/// I2C device on a shared bus.
pub struct I2cDevice<'a, M: RawMutex, BUS> {
bus: &'a Mutex<M, RefCell<BUS>>,
}
impl<'a, M: RawMutex, BUS> I2cDevice<'a, M, BUS> {
/// Create a new `I2cDevice`.
pub fn new(bus: &'a Mutex<M, RefCell<BUS>>) -> Self {
Self { bus }
}
}
impl<'a, M: RawMutex, BUS> ErrorType for I2cDevice<'a, M, BUS>
where
BUS: ErrorType,
{
type Error = I2cDeviceError<BUS::Error>;
}
impl<M, BUS> I2c for I2cDevice<'_, M, BUS>
where
M: RawMutex,
BUS: I2c,
{
fn read(&mut self, address: u8, buffer: &mut [u8]) -> Result<(), Self::Error> {
self.bus
.lock(|bus| bus.borrow_mut().read(address, buffer).map_err(I2cDeviceError::I2c))
}
fn write(&mut self, address: u8, bytes: &[u8]) -> Result<(), Self::Error> {
self.bus
.lock(|bus| bus.borrow_mut().write(address, bytes).map_err(I2cDeviceError::I2c))
}
fn write_read(&mut self, address: u8, wr_buffer: &[u8], rd_buffer: &mut [u8]) -> Result<(), Self::Error> {
self.bus.lock(|bus| {
bus.borrow_mut()
.write_read(address, wr_buffer, rd_buffer)
.map_err(I2cDeviceError::I2c)
})
}
fn transaction<'a>(&mut self, address: u8, operations: &mut [Operation<'a>]) -> Result<(), Self::Error> {
self.bus.lock(|bus| {
bus.borrow_mut()
.transaction(address, operations)
.map_err(I2cDeviceError::I2c)
})
}
}
impl<'a, M, BUS, E> embedded_hal_02::blocking::i2c::Write for I2cDevice<'_, M, BUS>
where
M: RawMutex,
BUS: embedded_hal_02::blocking::i2c::Write<Error = E>,
{
type Error = I2cDeviceError<E>;
fn write<'w>(&mut self, addr: u8, bytes: &'w [u8]) -> Result<(), Self::Error> {
self.bus
.lock(|bus| bus.borrow_mut().write(addr, bytes).map_err(I2cDeviceError::I2c))
}
}
impl<'a, M, BUS, E> embedded_hal_02::blocking::i2c::Read for I2cDevice<'_, M, BUS>
where
M: RawMutex,
BUS: embedded_hal_02::blocking::i2c::Read<Error = E>,
{
type Error = I2cDeviceError<E>;
fn read<'w>(&mut self, addr: u8, bytes: &'w mut [u8]) -> Result<(), Self::Error> {
self.bus
.lock(|bus| bus.borrow_mut().read(addr, bytes).map_err(I2cDeviceError::I2c))
}
}
impl<'a, M, BUS, E> embedded_hal_02::blocking::i2c::WriteRead for I2cDevice<'_, M, BUS>
where
M: RawMutex,
BUS: embedded_hal_02::blocking::i2c::WriteRead<Error = E>,
{
type Error = I2cDeviceError<E>;
fn write_read<'w>(&mut self, addr: u8, bytes: &'w [u8], buffer: &'w mut [u8]) -> Result<(), Self::Error> {
self.bus.lock(|bus| {
bus.borrow_mut()
.write_read(addr, bytes, buffer)
.map_err(I2cDeviceError::I2c)
})
}
}
/// I2C device on a shared bus, with its own configuration.
///
/// This is like [`I2cDevice`], with an additional bus configuration that's applied
/// to the bus before each use using [`SetConfig`]. This allows different
/// devices on the same bus to use different communication settings.
pub struct I2cDeviceWithConfig<'a, M: RawMutex, BUS: SetConfig> {
bus: &'a Mutex<M, RefCell<BUS>>,
config: BUS::Config,
}
impl<'a, M: RawMutex, BUS: SetConfig> I2cDeviceWithConfig<'a, M, BUS> {
/// Create a new `I2cDeviceWithConfig`.
pub fn new(bus: &'a Mutex<M, RefCell<BUS>>, config: BUS::Config) -> Self {
Self { bus, config }
}
/// Change the device's config at runtime
pub fn set_config(&mut self, config: BUS::Config) {
self.config = config;
}
}
impl<'a, M, BUS> ErrorType for I2cDeviceWithConfig<'a, M, BUS>
where
M: RawMutex,
BUS: ErrorType + SetConfig,
{
type Error = I2cDeviceError<BUS::Error>;
}
impl<M, BUS> I2c for I2cDeviceWithConfig<'_, M, BUS>
where
M: RawMutex,
BUS: I2c + SetConfig,
{
fn read(&mut self, address: u8, buffer: &mut [u8]) -> Result<(), Self::Error> {
self.bus.lock(|bus| {
let mut bus = bus.borrow_mut();
bus.set_config(&self.config).map_err(|_| I2cDeviceError::Config)?;
bus.read(address, buffer).map_err(I2cDeviceError::I2c)
})
}
fn write(&mut self, address: u8, bytes: &[u8]) -> Result<(), Self::Error> {
self.bus.lock(|bus| {
let mut bus = bus.borrow_mut();
bus.set_config(&self.config).map_err(|_| I2cDeviceError::Config)?;
bus.write(address, bytes).map_err(I2cDeviceError::I2c)
})
}
fn write_read(&mut self, address: u8, wr_buffer: &[u8], rd_buffer: &mut [u8]) -> Result<(), Self::Error> {
self.bus.lock(|bus| {
let mut bus = bus.borrow_mut();
bus.set_config(&self.config).map_err(|_| I2cDeviceError::Config)?;
bus.write_read(address, wr_buffer, rd_buffer)
.map_err(I2cDeviceError::I2c)
})
}
fn transaction<'a>(&mut self, address: u8, operations: &mut [Operation<'a>]) -> Result<(), Self::Error> {
self.bus.lock(|bus| {
let mut bus = bus.borrow_mut();
bus.set_config(&self.config).map_err(|_| I2cDeviceError::Config)?;
bus.transaction(address, operations).map_err(I2cDeviceError::I2c)
})
}
}

View File

@ -0,0 +1,3 @@
//! Blocking shared bus implementations for embedded-hal
pub mod i2c;
pub mod spi;

View File

@ -0,0 +1,167 @@
//! Blocking shared SPI bus
//!
//! # Example (nrf52)
//!
//! ```rust,ignore
//! use embassy_embedded_hal::shared_bus::blocking::spi::SpiDevice;
//! use embassy_sync::blocking_mutex::{NoopMutex, raw::NoopRawMutex};
//!
//! static SPI_BUS: StaticCell<NoopMutex<RefCell<Spim<SPI3>>>> = StaticCell::new();
//! let spi = Spim::new_txonly(p.SPI3, Irqs, p.P0_15, p.P0_18, Config::default());
//! let spi_bus = NoopMutex::new(RefCell::new(spi));
//! let spi_bus = SPI_BUS.init(spi_bus);
//!
//! // Device 1, using embedded-hal compatible driver for ST7735 LCD display
//! let cs_pin1 = Output::new(p.P0_24, Level::Low, OutputDrive::Standard);
//! let spi_dev1 = SpiDevice::new(spi_bus, cs_pin1);
//! let display1 = ST7735::new(spi_dev1, dc1, rst1, Default::default(), false, 160, 128);
//! ```
use core::cell::RefCell;
use embassy_sync::blocking_mutex::raw::RawMutex;
use embassy_sync::blocking_mutex::Mutex;
use embedded_hal_1::digital::OutputPin;
use embedded_hal_1::spi::{self, Operation, SpiBus};
use crate::shared_bus::SpiDeviceError;
use crate::SetConfig;
/// SPI device on a shared bus.
pub struct SpiDevice<'a, M: RawMutex, BUS, CS> {
bus: &'a Mutex<M, RefCell<BUS>>,
cs: CS,
}
impl<'a, M: RawMutex, BUS, CS> SpiDevice<'a, M, BUS, CS> {
/// Create a new `SpiDevice`.
pub fn new(bus: &'a Mutex<M, RefCell<BUS>>, cs: CS) -> Self {
Self { bus, cs }
}
}
impl<'a, M: RawMutex, BUS, CS> spi::ErrorType for SpiDevice<'a, M, BUS, CS>
where
BUS: spi::ErrorType,
CS: OutputPin,
{
type Error = SpiDeviceError<BUS::Error, CS::Error>;
}
impl<BUS, M, CS, Word> embedded_hal_1::spi::SpiDevice<Word> for SpiDevice<'_, M, BUS, CS>
where
M: RawMutex,
BUS: SpiBus<Word>,
CS: OutputPin,
Word: Copy + 'static,
{
fn transaction(&mut self, operations: &mut [embedded_hal_1::spi::Operation<'_, Word>]) -> Result<(), Self::Error> {
if cfg!(not(feature = "time")) && operations.iter().any(|op| matches!(op, Operation::DelayNs(_))) {
return Err(SpiDeviceError::DelayNotSupported);
}
self.bus.lock(|bus| {
let mut bus = bus.borrow_mut();
self.cs.set_low().map_err(SpiDeviceError::Cs)?;
let op_res = operations.iter_mut().try_for_each(|op| match op {
Operation::Read(buf) => bus.read(buf),
Operation::Write(buf) => bus.write(buf),
Operation::Transfer(read, write) => bus.transfer(read, write),
Operation::TransferInPlace(buf) => bus.transfer_in_place(buf),
#[cfg(not(feature = "time"))]
Operation::DelayNs(_) => unreachable!(),
#[cfg(feature = "time")]
Operation::DelayNs(ns) => {
embassy_time::block_for(embassy_time::Duration::from_nanos(*ns as _));
Ok(())
}
});
// On failure, it's important to still flush and deassert CS.
let flush_res = bus.flush();
let cs_res = self.cs.set_high();
let op_res = op_res.map_err(SpiDeviceError::Spi)?;
flush_res.map_err(SpiDeviceError::Spi)?;
cs_res.map_err(SpiDeviceError::Cs)?;
Ok(op_res)
})
}
}
/// SPI device on a shared bus, with its own configuration.
///
/// This is like [`SpiDevice`], with an additional bus configuration that's applied
/// to the bus before each use using [`SetConfig`]. This allows different
/// devices on the same bus to use different communication settings.
pub struct SpiDeviceWithConfig<'a, M: RawMutex, BUS: SetConfig, CS> {
bus: &'a Mutex<M, RefCell<BUS>>,
cs: CS,
config: BUS::Config,
}
impl<'a, M: RawMutex, BUS: SetConfig, CS> SpiDeviceWithConfig<'a, M, BUS, CS> {
/// Create a new `SpiDeviceWithConfig`.
pub fn new(bus: &'a Mutex<M, RefCell<BUS>>, cs: CS, config: BUS::Config) -> Self {
Self { bus, cs, config }
}
/// Change the device's config at runtime
pub fn set_config(&mut self, config: BUS::Config) {
self.config = config;
}
}
impl<'a, M, BUS, CS> spi::ErrorType for SpiDeviceWithConfig<'a, M, BUS, CS>
where
M: RawMutex,
BUS: spi::ErrorType + SetConfig,
CS: OutputPin,
{
type Error = SpiDeviceError<BUS::Error, CS::Error>;
}
impl<BUS, M, CS, Word> embedded_hal_1::spi::SpiDevice<Word> for SpiDeviceWithConfig<'_, M, BUS, CS>
where
M: RawMutex,
BUS: SpiBus<Word> + SetConfig,
CS: OutputPin,
Word: Copy + 'static,
{
fn transaction(&mut self, operations: &mut [embedded_hal_1::spi::Operation<'_, Word>]) -> Result<(), Self::Error> {
if cfg!(not(feature = "time")) && operations.iter().any(|op| matches!(op, Operation::DelayNs(_))) {
return Err(SpiDeviceError::DelayNotSupported);
}
self.bus.lock(|bus| {
let mut bus = bus.borrow_mut();
bus.set_config(&self.config).map_err(|_| SpiDeviceError::Config)?;
self.cs.set_low().map_err(SpiDeviceError::Cs)?;
let op_res = operations.iter_mut().try_for_each(|op| match op {
Operation::Read(buf) => bus.read(buf),
Operation::Write(buf) => bus.write(buf),
Operation::Transfer(read, write) => bus.transfer(read, write),
Operation::TransferInPlace(buf) => bus.transfer_in_place(buf),
#[cfg(not(feature = "time"))]
Operation::DelayNs(_) => unreachable!(),
#[cfg(feature = "time")]
Operation::DelayNs(ns) => {
embassy_time::block_for(embassy_time::Duration::from_nanos(*ns as _));
Ok(())
}
});
// On failure, it's important to still flush and deassert CS.
let flush_res = bus.flush();
let cs_res = self.cs.set_high();
let op_res = op_res.map_err(SpiDeviceError::Spi)?;
flush_res.map_err(SpiDeviceError::Spi)?;
cs_res.map_err(SpiDeviceError::Cs)?;
Ok(op_res)
})
}
}

View File

@ -0,0 +1,59 @@
//! Shared bus implementations
use core::fmt::Debug;
use embedded_hal_1::{i2c, spi};
pub mod asynch;
pub mod blocking;
/// Error returned by I2C device implementations in this crate.
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum I2cDeviceError<BUS> {
/// An operation on the inner I2C bus failed.
I2c(BUS),
/// Configuration of the inner I2C bus failed.
Config,
}
impl<BUS> i2c::Error for I2cDeviceError<BUS>
where
BUS: i2c::Error + Debug,
{
fn kind(&self) -> i2c::ErrorKind {
match self {
Self::I2c(e) => e.kind(),
Self::Config => i2c::ErrorKind::Other,
}
}
}
/// Error returned by SPI device implementations in this crate.
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[non_exhaustive]
pub enum SpiDeviceError<BUS, CS> {
/// An operation on the inner SPI bus failed.
Spi(BUS),
/// Setting the value of the Chip Select (CS) pin failed.
Cs(CS),
/// Delay operations are not supported when the `time` Cargo feature is not enabled.
DelayNotSupported,
/// The SPI bus could not be configured.
Config,
}
impl<BUS, CS> spi::Error for SpiDeviceError<BUS, CS>
where
BUS: spi::Error + Debug,
CS: Debug,
{
fn kind(&self) -> spi::ErrorKind {
match self {
Self::Spi(e) => e.kind(),
Self::Cs(_) => spi::ErrorKind::Other,
Self::DelayNotSupported => spi::ErrorKind::Other,
Self::Config => spi::ErrorKind::Other,
}
}
}

View File

@ -0,0 +1,25 @@
[package]
name = "embassy-executor-macros"
version = "0.6.2"
edition = "2021"
license = "MIT OR Apache-2.0"
description = "macros for creating the entry point and tasks for embassy-executor"
repository = "https://github.com/embassy-rs/embassy"
documentation = "https://docs.embassy.dev/embassy-executor-macros"
categories = [
"embedded",
"no-std",
"asynchronous",
]
[dependencies]
syn = { version = "2.0.15", features = ["full", "visit"] }
quote = "1.0.9"
darling = "0.20.1"
proc-macro2 = "1.0.29"
[lib]
proc-macro = true
[features]
nightly = []

View File

@ -0,0 +1,5 @@
# embassy-executor-macros
An [Embassy](https://embassy.dev) project.
NOTE: Do not use this crate directly. The macros are re-exported by `embassy-executor`.

View File

@ -0,0 +1,175 @@
#![doc = include_str!("../README.md")]
extern crate proc_macro;
use proc_macro::TokenStream;
mod macros;
mod util;
use macros::*;
/// Declares an async task that can be run by `embassy-executor`. The optional `pool_size` parameter can be used to specify how
/// many concurrent tasks can be spawned (default is 1) for the function.
///
///
/// The following restrictions apply:
///
/// * The function must be declared `async`.
/// * The function must not use generics.
/// * The optional `pool_size` attribute must be 1 or greater.
///
///
/// ## Examples
///
/// Declaring a task taking no arguments:
///
/// ``` rust
/// #[embassy_executor::task]
/// async fn mytask() {
/// // Function body
/// }
/// ```
///
/// Declaring a task with a given pool size:
///
/// ``` rust
/// #[embassy_executor::task(pool_size = 4)]
/// async fn mytask() {
/// // Function body
/// }
/// ```
#[proc_macro_attribute]
pub fn task(args: TokenStream, item: TokenStream) -> TokenStream {
task::run(args.into(), item.into()).into()
}
#[proc_macro_attribute]
pub fn main_avr(args: TokenStream, item: TokenStream) -> TokenStream {
main::run(args.into(), item.into(), &main::ARCH_AVR).into()
}
/// Creates a new `executor` instance and declares an application entry point for Cortex-M spawning the corresponding function body as an async task.
///
/// The following restrictions apply:
///
/// * The function must accept exactly 1 parameter, an `embassy_executor::Spawner` handle that it can use to spawn additional tasks.
/// * The function must be declared `async`.
/// * The function must not use generics.
/// * Only a single `main` task may be declared.
///
/// ## Examples
/// Spawning a task:
///
/// ``` rust
/// #[embassy_executor::main]
/// async fn main(_s: embassy_executor::Spawner) {
/// // Function body
/// }
/// ```
#[proc_macro_attribute]
pub fn main_cortex_m(args: TokenStream, item: TokenStream) -> TokenStream {
main::run(args.into(), item.into(), &main::ARCH_CORTEX_M).into()
}
/// Creates a new `executor` instance and declares an architecture agnostic application entry point spawning
/// the corresponding function body as an async task.
///
/// The following restrictions apply:
///
/// * The function must accept exactly 1 parameter, an `embassy_executor::Spawner` handle that it can use to spawn additional tasks.
/// * The function must be declared `async`.
/// * The function must not use generics.
/// * Only a single `main` task may be declared.
///
/// A user-defined entry macro must provided via the `entry` argument
///
/// ## Examples
/// Spawning a task:
/// ``` rust
/// #[embassy_executor::main(entry = "qingke_rt::entry")]
/// async fn main(_s: embassy_executor::Spawner) {
/// // Function body
/// }
/// ```
#[proc_macro_attribute]
pub fn main_spin(args: TokenStream, item: TokenStream) -> TokenStream {
main::run(args.into(), item.into(), &main::ARCH_SPIN).into()
}
/// Creates a new `executor` instance and declares an application entry point for RISC-V spawning the corresponding function body as an async task.
///
/// The following restrictions apply:
///
/// * The function must accept exactly 1 parameter, an `embassy_executor::Spawner` handle that it can use to spawn additional tasks.
/// * The function must be declared `async`.
/// * The function must not use generics.
/// * Only a single `main` task may be declared.
///
/// A user-defined entry macro can be optionally provided via the `entry` argument to override the default of `riscv_rt::entry`.
///
/// ## Examples
/// Spawning a task:
///
/// ``` rust
/// #[embassy_executor::main]
/// async fn main(_s: embassy_executor::Spawner) {
/// // Function body
/// }
/// ```
///
/// Spawning a task using a custom entry macro:
/// ``` rust
/// #[embassy_executor::main(entry = "esp_riscv_rt::entry")]
/// async fn main(_s: embassy_executor::Spawner) {
/// // Function body
/// }
/// ```
#[proc_macro_attribute]
pub fn main_riscv(args: TokenStream, item: TokenStream) -> TokenStream {
main::run(args.into(), item.into(), &main::ARCH_RISCV).into()
}
/// Creates a new `executor` instance and declares an application entry point for STD spawning the corresponding function body as an async task.
///
/// The following restrictions apply:
///
/// * The function must accept exactly 1 parameter, an `embassy_executor::Spawner` handle that it can use to spawn additional tasks.
/// * The function must be declared `async`.
/// * The function must not use generics.
/// * Only a single `main` task may be declared.
///
/// ## Examples
/// Spawning a task:
///
/// ``` rust
/// #[embassy_executor::main]
/// async fn main(_s: embassy_executor::Spawner) {
/// // Function body
/// }
/// ```
#[proc_macro_attribute]
pub fn main_std(args: TokenStream, item: TokenStream) -> TokenStream {
main::run(args.into(), item.into(), &main::ARCH_STD).into()
}
/// Creates a new `executor` instance and declares an application entry point for WASM spawning the corresponding function body as an async task.
///
/// The following restrictions apply:
///
/// * The function must accept exactly 1 parameter, an `embassy_executor::Spawner` handle that it can use to spawn additional tasks.
/// * The function must be declared `async`.
/// * The function must not use generics.
/// * Only a single `main` task may be declared.
///
/// ## Examples
/// Spawning a task:
///
/// ``` rust
/// #[embassy_executor::main]
/// async fn main(_s: embassy_executor::Spawner) {
/// // Function body
/// }
/// ```
#[proc_macro_attribute]
pub fn main_wasm(args: TokenStream, item: TokenStream) -> TokenStream {
main::run(args.into(), item.into(), &main::ARCH_WASM).into()
}

View File

@ -0,0 +1,184 @@
use std::str::FromStr;
use darling::export::NestedMeta;
use darling::FromMeta;
use proc_macro2::TokenStream;
use quote::quote;
use syn::{ReturnType, Type};
use crate::util::*;
enum Flavor {
Standard,
Wasm,
}
pub(crate) struct Arch {
default_entry: Option<&'static str>,
flavor: Flavor,
}
pub static ARCH_AVR: Arch = Arch {
default_entry: Some("avr_device::entry"),
flavor: Flavor::Standard,
};
pub static ARCH_RISCV: Arch = Arch {
default_entry: Some("riscv_rt::entry"),
flavor: Flavor::Standard,
};
pub static ARCH_CORTEX_M: Arch = Arch {
default_entry: Some("cortex_m_rt::entry"),
flavor: Flavor::Standard,
};
pub static ARCH_SPIN: Arch = Arch {
default_entry: None,
flavor: Flavor::Standard,
};
pub static ARCH_STD: Arch = Arch {
default_entry: None,
flavor: Flavor::Standard,
};
pub static ARCH_WASM: Arch = Arch {
default_entry: Some("wasm_bindgen::prelude::wasm_bindgen(start)"),
flavor: Flavor::Wasm,
};
#[derive(Debug, FromMeta, Default)]
struct Args {
#[darling(default)]
entry: Option<String>,
}
pub fn run(args: TokenStream, item: TokenStream, arch: &Arch) -> TokenStream {
let mut errors = TokenStream::new();
// If any of the steps for this macro fail, we still want to expand to an item that is as close
// to the expected output as possible. This helps out IDEs such that completions and other
// related features keep working.
let f: ItemFn = match syn::parse2(item.clone()) {
Ok(x) => x,
Err(e) => return token_stream_with_error(item, e),
};
let args = match NestedMeta::parse_meta_list(args) {
Ok(x) => x,
Err(e) => return token_stream_with_error(item, e),
};
let args = match Args::from_list(&args) {
Ok(x) => x,
Err(e) => {
errors.extend(e.write_errors());
Args::default()
}
};
let fargs = f.sig.inputs.clone();
if f.sig.asyncness.is_none() {
error(&mut errors, &f.sig, "main function must be async");
}
if !f.sig.generics.params.is_empty() {
error(&mut errors, &f.sig, "main function must not be generic");
}
if !f.sig.generics.where_clause.is_none() {
error(&mut errors, &f.sig, "main function must not have `where` clauses");
}
if !f.sig.abi.is_none() {
error(&mut errors, &f.sig, "main function must not have an ABI qualifier");
}
if !f.sig.variadic.is_none() {
error(&mut errors, &f.sig, "main function must not be variadic");
}
match &f.sig.output {
ReturnType::Default => {}
ReturnType::Type(_, ty) => match &**ty {
Type::Tuple(tuple) if tuple.elems.is_empty() => {}
Type::Never(_) => {}
_ => error(
&mut errors,
&f.sig,
"main function must either not return a value, return `()` or return `!`",
),
},
}
if fargs.len() != 1 {
error(&mut errors, &f.sig, "main function must have 1 argument: the spawner.");
}
let entry = match args.entry.as_deref().or(arch.default_entry) {
None => TokenStream::new(),
Some(x) => match TokenStream::from_str(x) {
Ok(x) => quote!(#[#x]),
Err(e) => {
error(&mut errors, &f.sig, e);
TokenStream::new()
}
},
};
let f_body = f.body;
let out = &f.sig.output;
let (main_ret, mut main_body) = match arch.flavor {
Flavor::Standard => (
quote!(!),
quote! {
unsafe fn __make_static<T>(t: &mut T) -> &'static mut T {
::core::mem::transmute(t)
}
let mut executor = ::embassy_executor::Executor::new();
let executor = unsafe { __make_static(&mut executor) };
executor.run(|spawner| {
spawner.must_spawn(__embassy_main(spawner));
})
},
),
Flavor::Wasm => (
quote!(Result<(), wasm_bindgen::JsValue>),
quote! {
let executor = ::std::boxed::Box::leak(::std::boxed::Box::new(::embassy_executor::Executor::new()));
executor.start(|spawner| {
spawner.must_spawn(__embassy_main(spawner));
});
Ok(())
},
),
};
let mut main_attrs = TokenStream::new();
for attr in f.attrs {
main_attrs.extend(quote!(#attr));
}
if !errors.is_empty() {
main_body = quote! {loop{}};
}
let result = quote! {
#[::embassy_executor::task()]
#[allow(clippy::future_not_send)]
async fn __embassy_main(#fargs) #out {
#f_body
}
#entry
#main_attrs
fn main() -> #main_ret {
#main_body
}
#errors
};
result
}

View File

@ -0,0 +1,2 @@
pub mod main;
pub mod task;

View File

@ -0,0 +1,220 @@
use std::str::FromStr;
use darling::export::NestedMeta;
use darling::FromMeta;
use proc_macro2::{Span, TokenStream};
use quote::{format_ident, quote};
use syn::visit::{self, Visit};
use syn::{Expr, ExprLit, Lit, LitInt, ReturnType, Type};
use crate::util::*;
#[derive(Debug, FromMeta, Default)]
struct Args {
#[darling(default)]
pool_size: Option<syn::Expr>,
/// Use this to override the `embassy_executor` crate path. Defaults to `::embassy_executor`.
#[darling(default)]
embassy_executor: Option<syn::Expr>,
}
pub fn run(args: TokenStream, item: TokenStream) -> TokenStream {
let mut errors = TokenStream::new();
// If any of the steps for this macro fail, we still want to expand to an item that is as close
// to the expected output as possible. This helps out IDEs such that completions and other
// related features keep working.
let f: ItemFn = match syn::parse2(item.clone()) {
Ok(x) => x,
Err(e) => return token_stream_with_error(item, e),
};
let args = match NestedMeta::parse_meta_list(args) {
Ok(x) => x,
Err(e) => return token_stream_with_error(item, e),
};
let args = match Args::from_list(&args) {
Ok(x) => x,
Err(e) => {
errors.extend(e.write_errors());
Args::default()
}
};
let pool_size = args.pool_size.unwrap_or(Expr::Lit(ExprLit {
attrs: vec![],
lit: Lit::Int(LitInt::new("1", Span::call_site())),
}));
let embassy_executor = args
.embassy_executor
.unwrap_or(Expr::Verbatim(TokenStream::from_str("::embassy_executor").unwrap()));
if f.sig.asyncness.is_none() {
error(&mut errors, &f.sig, "task functions must be async");
}
if !f.sig.generics.params.is_empty() {
error(&mut errors, &f.sig, "task functions must not be generic");
}
if !f.sig.generics.where_clause.is_none() {
error(&mut errors, &f.sig, "task functions must not have `where` clauses");
}
if !f.sig.abi.is_none() {
error(&mut errors, &f.sig, "task functions must not have an ABI qualifier");
}
if !f.sig.variadic.is_none() {
error(&mut errors, &f.sig, "task functions must not be variadic");
}
match &f.sig.output {
ReturnType::Default => {}
ReturnType::Type(_, ty) => match &**ty {
Type::Tuple(tuple) if tuple.elems.is_empty() => {}
Type::Never(_) => {}
_ => error(
&mut errors,
&f.sig,
"task functions must either not return a value, return `()` or return `!`",
),
},
}
let mut args = Vec::new();
let mut fargs = f.sig.inputs.clone();
for arg in fargs.iter_mut() {
match arg {
syn::FnArg::Receiver(_) => {
error(&mut errors, arg, "task functions must not have `self` arguments");
}
syn::FnArg::Typed(t) => {
check_arg_ty(&mut errors, &t.ty);
match t.pat.as_mut() {
syn::Pat::Ident(id) => {
id.mutability = None;
args.push((id.clone(), t.attrs.clone()));
}
_ => {
error(
&mut errors,
arg,
"pattern matching in task arguments is not yet supported",
);
}
}
}
}
}
let task_ident = f.sig.ident.clone();
let task_inner_ident = format_ident!("__{}_task", task_ident);
let mut task_inner = f.clone();
let visibility = task_inner.vis.clone();
task_inner.vis = syn::Visibility::Inherited;
task_inner.sig.ident = task_inner_ident.clone();
// assemble the original input arguments,
// including any attributes that may have
// been applied previously
let mut full_args = Vec::new();
for (arg, cfgs) in args {
full_args.push(quote!(
#(#cfgs)*
#arg
));
}
#[cfg(feature = "nightly")]
let mut task_outer_body = quote! {
trait _EmbassyInternalTaskTrait {
type Fut: ::core::future::Future + 'static;
fn construct(#fargs) -> Self::Fut;
}
impl _EmbassyInternalTaskTrait for () {
type Fut = impl core::future::Future + 'static;
fn construct(#fargs) -> Self::Fut {
#task_inner_ident(#(#full_args,)*)
}
}
const POOL_SIZE: usize = #pool_size;
static POOL: #embassy_executor::raw::TaskPool<<() as _EmbassyInternalTaskTrait>::Fut, POOL_SIZE> = #embassy_executor::raw::TaskPool::new();
unsafe { POOL._spawn_async_fn(move || <() as _EmbassyInternalTaskTrait>::construct(#(#full_args,)*)) }
};
#[cfg(not(feature = "nightly"))]
let mut task_outer_body = quote! {
const POOL_SIZE: usize = #pool_size;
static POOL: #embassy_executor::_export::TaskPoolRef = #embassy_executor::_export::TaskPoolRef::new();
unsafe { POOL.get::<_, POOL_SIZE>()._spawn_async_fn(move || #task_inner_ident(#(#full_args,)*)) }
};
let task_outer_attrs = task_inner.attrs.clone();
if !errors.is_empty() {
task_outer_body = quote! {
#![allow(unused_variables, unreachable_code)]
let _x: #embassy_executor::SpawnToken<()> = ::core::todo!();
_x
};
}
// Copy the generics + where clause to avoid more spurious errors.
let generics = &f.sig.generics;
let where_clause = &f.sig.generics.where_clause;
let result = quote! {
// This is the user's task function, renamed.
// We put it outside the #task_ident fn below, because otherwise
// the items defined there (such as POOL) would be in scope
// in the user's code.
#[doc(hidden)]
#task_inner
#(#task_outer_attrs)*
#visibility fn #task_ident #generics (#fargs) -> #embassy_executor::SpawnToken<impl Sized> #where_clause{
#task_outer_body
}
#errors
};
result
}
fn check_arg_ty(errors: &mut TokenStream, ty: &Type) {
struct Visitor<'a> {
errors: &'a mut TokenStream,
}
impl<'a, 'ast> Visit<'ast> for Visitor<'a> {
fn visit_type_reference(&mut self, i: &'ast syn::TypeReference) {
// only check for elided lifetime here. If not elided, it's checked by `visit_lifetime`.
if i.lifetime.is_none() {
error(
self.errors,
i.and_token,
"Arguments for tasks must live forever. Try using the `'static` lifetime.",
)
}
visit::visit_type_reference(self, i);
}
fn visit_lifetime(&mut self, i: &'ast syn::Lifetime) {
if i.ident.to_string() != "static" {
error(
self.errors,
i,
"Arguments for tasks must live forever. Try using the `'static` lifetime.",
)
}
}
fn visit_type_impl_trait(&mut self, i: &'ast syn::TypeImplTrait) {
error(self.errors, i, "`impl Trait` is not allowed in task arguments. It is syntax sugar for generics, and tasks can't be generic.");
}
}
Visit::visit_type(&mut Visitor { errors }, ty);
}

View File

@ -0,0 +1,74 @@
use std::fmt::Display;
use proc_macro2::{TokenStream, TokenTree};
use quote::{ToTokens, TokenStreamExt};
use syn::parse::{Parse, ParseStream};
use syn::{braced, bracketed, token, AttrStyle, Attribute, Signature, Token, Visibility};
pub fn token_stream_with_error(mut tokens: TokenStream, error: syn::Error) -> TokenStream {
tokens.extend(error.into_compile_error());
tokens
}
pub fn error<A: ToTokens, T: Display>(s: &mut TokenStream, obj: A, msg: T) {
s.extend(syn::Error::new_spanned(obj.into_token_stream(), msg).into_compile_error())
}
/// Function signature and body.
///
/// Same as `syn`'s `ItemFn` except we keep the body as a TokenStream instead of
/// parsing it. This makes the macro not error if there's a syntax error in the body,
/// which helps IDE autocomplete work better.
#[derive(Debug, Clone)]
pub struct ItemFn {
pub attrs: Vec<Attribute>,
pub vis: Visibility,
pub sig: Signature,
pub brace_token: token::Brace,
pub body: TokenStream,
}
impl Parse for ItemFn {
fn parse(input: ParseStream) -> syn::Result<Self> {
let mut attrs = input.call(Attribute::parse_outer)?;
let vis: Visibility = input.parse()?;
let sig: Signature = input.parse()?;
let content;
let brace_token = braced!(content in input);
while content.peek(Token![#]) && content.peek2(Token![!]) {
let content2;
attrs.push(Attribute {
pound_token: content.parse()?,
style: AttrStyle::Inner(content.parse()?),
bracket_token: bracketed!(content2 in content),
meta: content2.parse()?,
});
}
let mut body = Vec::new();
while !content.is_empty() {
body.push(content.parse::<TokenTree>()?);
}
let body = body.into_iter().collect();
Ok(ItemFn {
attrs,
vis,
sig,
brace_token,
body,
})
}
}
impl ToTokens for ItemFn {
fn to_tokens(&self, tokens: &mut TokenStream) {
tokens.append_all(self.attrs.iter().filter(|a| matches!(a.style, AttrStyle::Outer)));
self.vis.to_tokens(tokens);
self.sig.to_tokens(tokens);
self.brace_token.surround(tokens, |tokens| {
tokens.append_all(self.body.clone());
});
}
}

View File

@ -0,0 +1,97 @@
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## 0.7.0 - 2025-01-02
- Performance optimizations.
- Remove feature `integrated-timers`. Starting with `embassy-time-driver` v0.2, `embassy-time` v0.4 the timer queue is now part of the time driver, so it's no longer the executor's responsibility. Therefore, `embassy-executor` no longer provides an `embassy-time-queue-driver` implementation.
- Added the possibility for timer driver implementations to store arbitrary data in task headers. This can be used to make a timer queue intrusive list, similar to the previous `integrated-timers` feature. Payload size is controlled by the `timer-item-payload-size-X` features.
- Added `TaskRef::executor` to obtain a reference to a task's executor
## 0.6.3 - 2024-11-12
- Building with the `nightly` feature now works with the Xtensa Rust compiler 1.82.
- Compare vtable address instead of contents. Saves 44 bytes of flash on cortex-m.
## 0.6.2 - 2024-11-06
- The `nightly` feature no longer requires `nightly-2024-09-06` or newer.
## 0.6.1 - 2024-10-21
- Soundness fix: Deny using `impl Trait` in task arguments. This was previously accidentally allowed when not using the `nightly` feature,
and could cause out of bounds memory accesses if spawning the same task mulitple times with different underlying types
for the `impl Trait`. Affected versions are 0.4.x, 0.5.0 and 0.6.0, which have been yanked.
- Add an architecture-agnostic executor that spins waiting for tasks to run, enabled with the `arch-spin` feature.
- Update for breaking change in the nightly waker_getters API. The `nightly` feature now requires `nightly-2024-09-06` or newer.
- Improve macro error messages.
## 0.6.0 - 2024-08-05
- Add collapse_debuginfo to fmt.rs macros.
- initial support for AVR
- use nightly waker_getters APIs
## 0.5.1 - 2024-10-21
- Soundness fix: Deny using `impl Trait` in task arguments. This was previously accidentally allowed when not using the `nightly` feature,
and could cause out of bounds memory accesses if spawning the same task mulitple times with different underlying types
for the `impl Trait`. Affected versions are 0.4.x, 0.5.0 and 0.6.0, which have been yanked.
## 0.5.0 - 2024-01-11
- Updated to `embassy-time-driver 0.1`, `embassy-time-queue-driver 0.1`, compatible with `embassy-time v0.3` and higher.
## 0.4.0 - 2023-12-05
- Removed `arch-xtensa`. Use the executor provided by the HAL crate (`esp-hal`, `esp32s3-hal`, etc...) instead.
- Added an arena allocator for tasks, allowing using the `main` and `task` macros on Rust 1.75 stable. (it is only used if the `nightly` feature is not enabled. When `nightly` is enabled, `type_alias_impl_trait` is used to statically allocate tasks, as before).
## 0.3.3 - 2023-11-15
- Add `main` macro reexport for Xtensa arch.
- Remove use of `atomic-polyfill`. The executor now has multiple implementations of its internal data structures for cases where the target supports atomics or doesn't.
## 0.3.2 - 2023-11-06
- Use `atomic-polyfill` for `riscv32`
- Removed unused dependencies (static_cell, futures-util)
## 0.3.1 - 2023-11-01
- Fix spurious "Found waker not created by the Embassy executor" error in recent nightlies.
## 0.3.0 - 2023-08-25
- Replaced Pender. Implementations now must define an extern function called `__pender`.
- Made `raw::AvailableTask` public
- Made `SpawnToken::new_failed` public
- You can now use arbitrary expressions to specify `#[task(pool_size = X)]`
## 0.2.1 - 2023-08-10
- Avoid calling `pend()` when waking expired timers
- Properly reset finished task state with `integrated-timers` enabled
- Introduce `InterruptExecutor::spawner()`
- Fix incorrect critical section in Xtensa executor
## 0.2.0 - 2023-04-27
- Replace unnecessary atomics in runqueue
- add Pender, rework Cargo features.
- add support for turbo-wakers.
- Allow TaskStorage to auto-implement `Sync`
- Use AtomicPtr for signal_ctx, removes 1 unsafe.
- Replace unsound critical sections with atomics
## 0.1.1 - 2022-11-23
- Fix features for documentation
## 0.1.0 - 2022-11-23
- First release

205
embassy-executor/Cargo.toml Normal file
View File

@ -0,0 +1,205 @@
[package]
name = "embassy-executor"
version = "0.7.0"
edition = "2021"
license = "MIT OR Apache-2.0"
description = "async/await executor designed for embedded usage"
repository = "https://github.com/embassy-rs/embassy"
documentation = "https://docs.embassy.dev/embassy-executor"
categories = [
"embedded",
"no-std",
"asynchronous",
]
[package.metadata.embassy_docs]
src_base = "https://github.com/embassy-rs/embassy/blob/embassy-executor-v$VERSION/embassy-executor/src/"
src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-executor/src/"
features = ["defmt"]
flavors = [
{ name = "std", target = "x86_64-unknown-linux-gnu", features = ["arch-std", "executor-thread"] },
{ name = "wasm", target = "wasm32-unknown-unknown", features = ["arch-wasm", "executor-thread"] },
{ name = "cortex-m", target = "thumbv7em-none-eabi", features = ["arch-cortex-m", "executor-thread", "executor-interrupt"] },
{ name = "riscv32", target = "riscv32imac-unknown-none-elf", features = ["arch-riscv32", "executor-thread"] },
]
[package.metadata.docs.rs]
default-target = "thumbv7em-none-eabi"
targets = ["thumbv7em-none-eabi"]
features = ["defmt", "arch-cortex-m", "executor-thread", "executor-interrupt"]
[dependencies]
defmt = { version = "0.3", optional = true }
log = { version = "0.4.14", optional = true }
rtos-trace = { version = "0.1.3", optional = true }
embassy-executor-macros = { version = "0.6.2", path = "../embassy-executor-macros" }
embassy-time-driver = { version = "0.2", path = "../embassy-time-driver", optional = true }
critical-section = "1.1"
document-features = "0.2.7"
# needed for AVR
portable-atomic = { version = "1.5", optional = true }
# arch-cortex-m dependencies
cortex-m = { version = "0.7.6", optional = true }
# arch-wasm dependencies
wasm-bindgen = { version = "0.2.82", optional = true }
js-sys = { version = "0.3", optional = true }
# arch-avr dependencies
avr-device = { version = "0.5.3", optional = true }
[dev-dependencies]
critical-section = { version = "1.1", features = ["std"] }
trybuild = "1.0"
embassy-sync = { path = "../embassy-sync" }
[features]
## Enable nightly-only features
nightly = ["embassy-executor-macros/nightly"]
# Enables turbo wakers, which requires patching core. Not surfaced in the docs by default due to
# being an complicated advanced and undocumented feature.
# See: https://github.com/embassy-rs/embassy/pull/1263
turbowakers = []
#! ### Architecture
_arch = [] # some arch was picked
## std
arch-std = ["_arch", "critical-section/std"]
## Cortex-M
arch-cortex-m = ["_arch", "dep:cortex-m"]
## RISC-V 32
arch-riscv32 = ["_arch"]
## WASM
arch-wasm = ["_arch", "dep:wasm-bindgen", "dep:js-sys", "critical-section/std"]
## AVR
arch-avr = ["_arch", "dep:portable-atomic", "dep:avr-device"]
## spin (architecture agnostic; never sleeps)
arch-spin = ["_arch"]
#! ### Executor
## Enable the thread-mode executor (using WFE/SEV in Cortex-M, WFI in other embedded archs)
executor-thread = []
## Enable the interrupt-mode executor (available in Cortex-M only)
executor-interrupt = []
## Enable tracing support (adds some overhead)
trace = []
## Enable support for rtos-trace framework
rtos-trace = ["dep:rtos-trace", "trace", "dep:embassy-time-driver"]
#! ### Timer Item Payload Size
#! Sets the size of the payload for timer items, allowing integrated timer implementors to store
#! additional data in the timer item. The payload field will be aligned to this value as well.
#! If these features are not defined, the timer item will contain no payload field.
_timer-item-payload = [] # A size was picked
## 1 bytes
timer-item-payload-size-1 = ["_timer-item-payload"]
## 2 bytes
timer-item-payload-size-2 = ["_timer-item-payload"]
## 4 bytes
timer-item-payload-size-4 = ["_timer-item-payload"]
## 8 bytes
timer-item-payload-size-8 = ["_timer-item-payload"]
#! ### Task Arena Size
#! Sets the [task arena](#task-arena) size. Necessary if youre not using `nightly`.
#!
#! <details>
#! <summary>Preconfigured Task Arena Sizes:</summary>
#! <!-- rustdoc requires the following blank line for the feature list to render correctly! -->
#!
# BEGIN AUTOGENERATED CONFIG FEATURES
# Generated by gen_config.py. DO NOT EDIT.
## 64
task-arena-size-64 = []
## 128
task-arena-size-128 = []
## 192
task-arena-size-192 = []
## 256
task-arena-size-256 = []
## 320
task-arena-size-320 = []
## 384
task-arena-size-384 = []
## 512
task-arena-size-512 = []
## 640
task-arena-size-640 = []
## 768
task-arena-size-768 = []
## 1024
task-arena-size-1024 = []
## 1280
task-arena-size-1280 = []
## 1536
task-arena-size-1536 = []
## 2048
task-arena-size-2048 = []
## 2560
task-arena-size-2560 = []
## 3072
task-arena-size-3072 = []
## 4096 (default)
task-arena-size-4096 = [] # Default
## 5120
task-arena-size-5120 = []
## 6144
task-arena-size-6144 = []
## 8192
task-arena-size-8192 = []
## 10240
task-arena-size-10240 = []
## 12288
task-arena-size-12288 = []
## 16384
task-arena-size-16384 = []
## 20480
task-arena-size-20480 = []
## 24576
task-arena-size-24576 = []
## 32768
task-arena-size-32768 = []
## 40960
task-arena-size-40960 = []
## 49152
task-arena-size-49152 = []
## 65536
task-arena-size-65536 = []
## 81920
task-arena-size-81920 = []
## 98304
task-arena-size-98304 = []
## 131072
task-arena-size-131072 = []
## 163840
task-arena-size-163840 = []
## 196608
task-arena-size-196608 = []
## 262144
task-arena-size-262144 = []
## 327680
task-arena-size-327680 = []
## 393216
task-arena-size-393216 = []
## 524288
task-arena-size-524288 = []
## 655360
task-arena-size-655360 = []
## 786432
task-arena-size-786432 = []
## 1048576
task-arena-size-1048576 = []
# END AUTOGENERATED CONFIG FEATURES
#! </details>

View File

@ -0,0 +1,37 @@
# embassy-executor
An async/await executor designed for embedded usage.
- No `alloc`, no heap needed.
- With nightly Rust, task futures can be fully statically allocated.
- No "fixed capacity" data structures, executor works with 1 or 1000 tasks without needing config/tuning.
- Integrated timer queue: sleeping is easy, just do `Timer::after_secs(1).await;`.
- No busy-loop polling: CPU sleeps when there's no work to do, using interrupts or `WFE/SEV`.
- Efficient polling: a wake will only poll the woken task, not all of them.
- Fair: a task can't monopolize CPU time even if it's constantly being woken. All other tasks get a chance to run before a given task gets polled for the second time.
- Creating multiple executor instances is supported, to run tasks with multiple priority levels. This allows higher-priority tasks to preempt lower-priority tasks.
## Task arena
When the `nightly` Cargo feature is not enabled, `embassy-executor` allocates tasks out of an arena (a very simple bump allocator).
If the task arena gets full, the program will panic at runtime. To guarantee this doesn't happen, you must set the size to the sum of sizes of all tasks.
Tasks are allocated from the arena when spawned for the first time. If the task exits, the allocation is not released to the arena, but can be reused to spawn the task again. For multiple-instance tasks (like `#[embassy_executor::task(pool_size = 4)]`), the first spawn will allocate memory for all instances. This is done for performance and to increase predictability (for example, spawning at least 1 instance of every task at boot guarantees an immediate panic if the arena is too small, while allocating instances on-demand could delay the panic to only when the program is under load).
The arena size can be configured in two ways:
- Via Cargo features: enable a Cargo feature like `task-arena-size-8192`. Only a selection of values
is available, see [Task Area Sizes](#task-arena-size) for reference.
- Via environment variables at build time: set the variable named `EMBASSY_EXECUTOR_TASK_ARENA_SIZE`. For example
`EMBASSY_EXECUTOR_TASK_ARENA_SIZE=4321 cargo build`. You can also set them in the `[env]` section of `.cargo/config.toml`.
Any value can be set, unlike with Cargo features.
Environment variables take precedence over Cargo features. If two Cargo features are enabled for the same setting
with different values, compilation fails.
## Statically allocating tasks
When using nightly Rust, enable the `nightly` Cargo feature. This will make `embassy-executor` use the `type_alias_impl_trait` feature to allocate all tasks in `static`s. Each task gets its own `static`, with the exact size to hold the task (or multiple instances of it, if using `pool_size`) calculated automatically at compile time. If tasks don't fit in RAM, this is detected at compile time by the linker. Runtime panics due to running out of memory are not possible.
The configured arena size is ignored, no arena is used at all.

99
embassy-executor/build.rs Normal file
View File

@ -0,0 +1,99 @@
use std::collections::HashMap;
use std::fmt::Write;
use std::path::PathBuf;
use std::{env, fs};
#[path = "./build_common.rs"]
mod common;
static CONFIGS: &[(&str, usize)] = &[
// BEGIN AUTOGENERATED CONFIG FEATURES
// Generated by gen_config.py. DO NOT EDIT.
("TASK_ARENA_SIZE", 4096),
// END AUTOGENERATED CONFIG FEATURES
];
struct ConfigState {
value: usize,
seen_feature: bool,
seen_env: bool,
}
fn main() {
let crate_name = env::var("CARGO_PKG_NAME")
.unwrap()
.to_ascii_uppercase()
.replace('-', "_");
// only rebuild if build.rs changed. Otherwise Cargo will rebuild if any
// other file changed.
println!("cargo:rerun-if-changed=build.rs");
// Rebuild if config envvar changed.
for (name, _) in CONFIGS {
println!("cargo:rerun-if-env-changed={crate_name}_{name}");
}
let mut configs = HashMap::new();
for (name, default) in CONFIGS {
configs.insert(
*name,
ConfigState {
value: *default,
seen_env: false,
seen_feature: false,
},
);
}
let prefix = format!("{crate_name}_");
for (var, value) in env::vars() {
if let Some(name) = var.strip_prefix(&prefix) {
let Some(cfg) = configs.get_mut(name) else {
panic!("Unknown env var {name}")
};
let Ok(value) = value.parse::<usize>() else {
panic!("Invalid value for env var {name}: {value}")
};
cfg.value = value;
cfg.seen_env = true;
}
if let Some(feature) = var.strip_prefix("CARGO_FEATURE_") {
if let Some(i) = feature.rfind('_') {
let name = &feature[..i];
let value = &feature[i + 1..];
if let Some(cfg) = configs.get_mut(name) {
let Ok(value) = value.parse::<usize>() else {
panic!("Invalid value for feature {name}: {value}")
};
// envvars take priority.
if !cfg.seen_env {
if cfg.seen_feature {
panic!("multiple values set for feature {}: {} and {}", name, cfg.value, value);
}
cfg.value = value;
cfg.seen_feature = true;
}
}
}
}
}
let mut data = String::new();
for (name, cfg) in &configs {
writeln!(&mut data, "pub const {}: usize = {};", name, cfg.value).unwrap();
}
let out_dir = PathBuf::from(env::var_os("OUT_DIR").unwrap());
let out_file = out_dir.join("config.rs").to_string_lossy().to_string();
fs::write(out_file, data).unwrap();
let mut rustc_cfgs = common::CfgSet::new();
common::set_target_cfgs(&mut rustc_cfgs);
}

View File

@ -0,0 +1,126 @@
// NOTE: this file is copy-pasted between several Embassy crates, because there is no
// straightforward way to share this code:
// - it cannot be placed into the root of the repo and linked from each build.rs using `#[path =
// "../build_common.rs"]`, because `cargo publish` requires that all files published with a crate
// reside in the crate's directory,
// - it cannot be symlinked from `embassy-xxx/build_common.rs` to `../build_common.rs`, because
// symlinks don't work on Windows.
use std::collections::HashSet;
use std::env;
/// Helper for emitting cargo instruction for enabling configs (`cargo:rustc-cfg=X`) and declaring
/// them (`cargo:rust-check-cfg=cfg(X)`).
#[derive(Debug)]
pub struct CfgSet {
enabled: HashSet<String>,
declared: HashSet<String>,
}
impl CfgSet {
pub fn new() -> Self {
Self {
enabled: HashSet::new(),
declared: HashSet::new(),
}
}
/// Enable a config, which can then be used in `#[cfg(...)]` for conditional compilation.
///
/// All configs that can potentially be enabled should be unconditionally declared using
/// [`Self::declare()`].
pub fn enable(&mut self, cfg: impl AsRef<str>) {
if self.enabled.insert(cfg.as_ref().to_owned()) {
println!("cargo:rustc-cfg={}", cfg.as_ref());
}
}
pub fn enable_all(&mut self, cfgs: &[impl AsRef<str>]) {
for cfg in cfgs.iter() {
self.enable(cfg.as_ref());
}
}
/// Declare a valid config for conditional compilation, without enabling it.
///
/// This enables rustc to check that the configs in `#[cfg(...)]` attributes are valid.
pub fn declare(&mut self, cfg: impl AsRef<str>) {
if self.declared.insert(cfg.as_ref().to_owned()) {
println!("cargo:rustc-check-cfg=cfg({})", cfg.as_ref());
}
}
pub fn declare_all(&mut self, cfgs: &[impl AsRef<str>]) {
for cfg in cfgs.iter() {
self.declare(cfg.as_ref());
}
}
pub fn set(&mut self, cfg: impl Into<String>, enable: bool) {
let cfg = cfg.into();
if enable {
self.enable(cfg.clone());
}
self.declare(cfg);
}
}
/// Sets configs that describe the target platform.
pub fn set_target_cfgs(cfgs: &mut CfgSet) {
let target = env::var("TARGET").unwrap();
if target.starts_with("thumbv6m-") {
cfgs.enable_all(&["cortex_m", "armv6m"]);
} else if target.starts_with("thumbv7m-") {
cfgs.enable_all(&["cortex_m", "armv7m"]);
} else if target.starts_with("thumbv7em-") {
cfgs.enable_all(&["cortex_m", "armv7m", "armv7em"]);
} else if target.starts_with("thumbv8m.base") {
cfgs.enable_all(&["cortex_m", "armv8m", "armv8m_base"]);
} else if target.starts_with("thumbv8m.main") {
cfgs.enable_all(&["cortex_m", "armv8m", "armv8m_main"]);
}
cfgs.declare_all(&[
"cortex_m",
"armv6m",
"armv7m",
"armv7em",
"armv8m",
"armv8m_base",
"armv8m_main",
]);
cfgs.set("has_fpu", target.ends_with("-eabihf"));
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub struct CompilerDate {
year: u16,
month: u8,
day: u8,
}
impl CompilerDate {
fn parse(date: &str) -> Option<Self> {
let mut parts = date.split('-');
let year = parts.next()?.parse().ok()?;
let month = parts.next()?.parse().ok()?;
let day = parts.next()?.parse().ok()?;
Some(Self { year, month, day })
}
}
impl PartialEq<&str> for CompilerDate {
fn eq(&self, other: &&str) -> bool {
let Some(other) = Self::parse(other) else {
return false;
};
self.eq(&other)
}
}
impl PartialOrd<&str> for CompilerDate {
fn partial_cmp(&self, other: &&str) -> Option<std::cmp::Ordering> {
Self::parse(other).map(|other| self.cmp(&other))
}
}

View File

@ -0,0 +1,88 @@
import os
abspath = os.path.abspath(__file__)
dname = os.path.dirname(abspath)
os.chdir(dname)
features = []
def feature(name, default, min=None, max=None, pow2=None, vals=None, factors=[]):
if vals is None:
assert min is not None
assert max is not None
vals = set()
val = min
while val <= max:
vals.add(val)
for f in factors:
if val * f <= max:
vals.add(val * f)
if (pow2 == True or (isinstance(pow2, int) and val >= pow2)) and val > 0:
val *= 2
else:
val += 1
vals.add(default)
vals = sorted(list(vals))
features.append(
{
"name": name,
"default": default,
"vals": vals,
}
)
feature(
"task_arena_size", default=4096, min=64, max=1024 * 1024, pow2=True, factors=[3, 5]
)
# ========= Update Cargo.toml
things = ""
for f in features:
name = f["name"].replace("_", "-")
for val in f["vals"]:
things += f"## {val}"
if val == f["default"]:
things += " (default)\n"
else:
things += "\n"
things += f"{name}-{val} = []"
if val == f["default"]:
things += " # Default"
things += "\n"
things += "\n"
SEPARATOR_START = "# BEGIN AUTOGENERATED CONFIG FEATURES\n"
SEPARATOR_END = "# END AUTOGENERATED CONFIG FEATURES\n"
HELP = "# Generated by gen_config.py. DO NOT EDIT.\n"
with open("Cargo.toml", "r") as f:
data = f.read()
before, data = data.split(SEPARATOR_START, maxsplit=1)
_, after = data.split(SEPARATOR_END, maxsplit=1)
data = before + SEPARATOR_START + HELP + things + SEPARATOR_END + after
with open("Cargo.toml", "w") as f:
f.write(data)
# ========= Update build.rs
things = ""
for f in features:
name = f["name"].upper()
things += f' ("{name}", {f["default"]}),\n'
SEPARATOR_START = "// BEGIN AUTOGENERATED CONFIG FEATURES\n"
SEPARATOR_END = "// END AUTOGENERATED CONFIG FEATURES\n"
HELP = " // Generated by gen_config.py. DO NOT EDIT.\n"
with open("build.rs", "r") as f:
data = f.read()
before, data = data.split(SEPARATOR_START, maxsplit=1)
_, after = data.split(SEPARATOR_END, maxsplit=1)
data = before + SEPARATOR_START + HELP + things + " " + SEPARATOR_END + after
with open("build.rs", "w") as f:
f.write(data)

View File

@ -0,0 +1,72 @@
#[cfg(feature = "executor-interrupt")]
compile_error!("`executor-interrupt` is not supported with `arch-avr`.");
#[cfg(feature = "executor-thread")]
pub use thread::*;
#[cfg(feature = "executor-thread")]
mod thread {
use core::marker::PhantomData;
pub use embassy_executor_macros::main_avr as main;
use portable_atomic::{AtomicBool, Ordering};
use crate::{raw, Spawner};
static SIGNAL_WORK_THREAD_MODE: AtomicBool = AtomicBool::new(false);
#[export_name = "__pender"]
fn __pender(_context: *mut ()) {
SIGNAL_WORK_THREAD_MODE.store(true, Ordering::SeqCst);
}
/// avr Executor
pub struct Executor {
inner: raw::Executor,
not_send: PhantomData<*mut ()>,
}
impl Executor {
/// Create a new Executor.
pub fn new() -> Self {
Self {
inner: raw::Executor::new(core::ptr::null_mut()),
not_send: PhantomData,
}
}
/// Run the executor.
///
/// The `init` closure is called with a [`Spawner`] that spawns tasks on
/// this executor. Use it to spawn the initial task(s). After `init` returns,
/// the executor starts running the tasks.
///
/// To spawn more tasks later, you may keep copies of the [`Spawner`] (it is `Copy`),
/// for example by passing it as an argument to the initial tasks.
///
/// This function requires `&'static mut self`. This means you have to store the
/// Executor instance in a place where it'll live forever and grants you mutable
/// access. There's a few ways to do this:
///
/// - a [StaticCell](https://docs.rs/static_cell/latest/static_cell/) (safe)
/// - a `static mut` (unsafe)
/// - a local variable in a function you know never returns (like `fn main() -> !`), upgrading its lifetime with `transmute`. (unsafe)
///
/// This function never returns.
pub fn run(&'static mut self, init: impl FnOnce(Spawner)) -> ! {
init(self.inner.spawner());
loop {
unsafe {
avr_device::interrupt::disable();
if !SIGNAL_WORK_THREAD_MODE.swap(false, Ordering::SeqCst) {
avr_device::interrupt::enable();
avr_device::asm::sleep();
} else {
avr_device::interrupt::enable();
self.inner.poll();
}
}
}
}
}
}

View File

@ -0,0 +1,231 @@
#[export_name = "__pender"]
#[cfg(any(feature = "executor-thread", feature = "executor-interrupt"))]
fn __pender(context: *mut ()) {
unsafe {
// Safety: `context` is either `usize::MAX` created by `Executor::run`, or a valid interrupt
// request number given to `InterruptExecutor::start`.
let context = context as usize;
#[cfg(feature = "executor-thread")]
// Try to make Rust optimize the branching away if we only use thread mode.
if !cfg!(feature = "executor-interrupt") || context == THREAD_PENDER {
core::arch::asm!("sev");
return;
}
#[cfg(feature = "executor-interrupt")]
{
use cortex_m::interrupt::InterruptNumber;
use cortex_m::peripheral::NVIC;
#[derive(Clone, Copy)]
struct Irq(u16);
unsafe impl InterruptNumber for Irq {
fn number(self) -> u16 {
self.0
}
}
let irq = Irq(context as u16);
// STIR is faster, but is only available in v7 and higher.
#[cfg(not(armv6m))]
{
let mut nvic: NVIC = core::mem::transmute(());
nvic.request(irq);
}
#[cfg(armv6m)]
NVIC::pend(irq);
}
}
}
#[cfg(feature = "executor-thread")]
pub use thread::*;
#[cfg(feature = "executor-thread")]
mod thread {
pub(super) const THREAD_PENDER: usize = usize::MAX;
use core::arch::asm;
use core::marker::PhantomData;
pub use embassy_executor_macros::main_cortex_m as main;
use crate::{raw, Spawner};
/// Thread mode executor, using WFE/SEV.
///
/// This is the simplest and most common kind of executor. It runs on
/// thread mode (at the lowest priority level), and uses the `WFE` ARM instruction
/// to sleep when it has no more work to do. When a task is woken, a `SEV` instruction
/// is executed, to make the `WFE` exit from sleep and poll the task.
///
/// This executor allows for ultra low power consumption for chips where `WFE`
/// triggers low-power sleep without extra steps. If your chip requires extra steps,
/// you may use [`raw::Executor`] directly to program custom behavior.
pub struct Executor {
inner: raw::Executor,
not_send: PhantomData<*mut ()>,
}
impl Executor {
/// Create a new Executor.
pub fn new() -> Self {
Self {
inner: raw::Executor::new(THREAD_PENDER as *mut ()),
not_send: PhantomData,
}
}
/// Run the executor.
///
/// The `init` closure is called with a [`Spawner`] that spawns tasks on
/// this executor. Use it to spawn the initial task(s). After `init` returns,
/// the executor starts running the tasks.
///
/// To spawn more tasks later, you may keep copies of the [`Spawner`] (it is `Copy`),
/// for example by passing it as an argument to the initial tasks.
///
/// This function requires `&'static mut self`. This means you have to store the
/// Executor instance in a place where it'll live forever and grants you mutable
/// access. There's a few ways to do this:
///
/// - a [StaticCell](https://docs.rs/static_cell/latest/static_cell/) (safe)
/// - a `static mut` (unsafe)
/// - a local variable in a function you know never returns (like `fn main() -> !`), upgrading its lifetime with `transmute`. (unsafe)
///
/// This function never returns.
pub fn run(&'static mut self, init: impl FnOnce(Spawner)) -> ! {
init(self.inner.spawner());
loop {
unsafe {
self.inner.poll();
asm!("wfe");
};
}
}
}
}
#[cfg(feature = "executor-interrupt")]
pub use interrupt::*;
#[cfg(feature = "executor-interrupt")]
mod interrupt {
use core::cell::{Cell, UnsafeCell};
use core::mem::MaybeUninit;
use cortex_m::interrupt::InterruptNumber;
use cortex_m::peripheral::NVIC;
use critical_section::Mutex;
use crate::raw;
/// Interrupt mode executor.
///
/// This executor runs tasks in interrupt mode. The interrupt handler is set up
/// to poll tasks, and when a task is woken the interrupt is pended from software.
///
/// This allows running async tasks at a priority higher than thread mode. One
/// use case is to leave thread mode free for non-async tasks. Another use case is
/// to run multiple executors: one in thread mode for low priority tasks and another in
/// interrupt mode for higher priority tasks. Higher priority tasks will preempt lower
/// priority ones.
///
/// It is even possible to run multiple interrupt mode executors at different priorities,
/// by assigning different priorities to the interrupts. For an example on how to do this,
/// See the 'multiprio' example for 'embassy-nrf'.
///
/// To use it, you have to pick an interrupt that won't be used by the hardware.
/// Some chips reserve some interrupts for this purpose, sometimes named "software interrupts" (SWI).
/// If this is not the case, you may use an interrupt from any unused peripheral.
///
/// It is somewhat more complex to use, it's recommended to use the thread-mode
/// [`Executor`] instead, if it works for your use case.
pub struct InterruptExecutor {
started: Mutex<Cell<bool>>,
executor: UnsafeCell<MaybeUninit<raw::Executor>>,
}
unsafe impl Send for InterruptExecutor {}
unsafe impl Sync for InterruptExecutor {}
impl InterruptExecutor {
/// Create a new, not started `InterruptExecutor`.
#[inline]
pub const fn new() -> Self {
Self {
started: Mutex::new(Cell::new(false)),
executor: UnsafeCell::new(MaybeUninit::uninit()),
}
}
/// Executor interrupt callback.
///
/// # Safety
///
/// - You MUST call this from the interrupt handler, and from nowhere else.
/// - You must not call this before calling `start()`.
pub unsafe fn on_interrupt(&'static self) {
let executor = unsafe { (&*self.executor.get()).assume_init_ref() };
executor.poll();
}
/// Start the executor.
///
/// This initializes the executor, enables the interrupt, and returns.
/// The executor keeps running in the background through the interrupt.
///
/// This returns a [`SendSpawner`] you can use to spawn tasks on it. A [`SendSpawner`]
/// is returned instead of a [`Spawner`](embassy_executor::Spawner) because the executor effectively runs in a
/// different "thread" (the interrupt), so spawning tasks on it is effectively
/// sending them.
///
/// To obtain a [`Spawner`](embassy_executor::Spawner) for this executor, use [`Spawner::for_current_executor()`](embassy_executor::Spawner::for_current_executor()) from
/// a task running in it.
///
/// # Interrupt requirements
///
/// You must write the interrupt handler yourself, and make it call [`on_interrupt()`](Self::on_interrupt).
///
/// This method already enables (unmasks) the interrupt, you must NOT do it yourself.
///
/// You must set the interrupt priority before calling this method. You MUST NOT
/// do it after.
///
pub fn start(&'static self, irq: impl InterruptNumber) -> crate::SendSpawner {
if critical_section::with(|cs| self.started.borrow(cs).replace(true)) {
panic!("InterruptExecutor::start() called multiple times on the same executor.");
}
unsafe {
(&mut *self.executor.get())
.as_mut_ptr()
.write(raw::Executor::new(irq.number() as *mut ()))
}
let executor = unsafe { (&*self.executor.get()).assume_init_ref() };
unsafe { NVIC::unmask(irq) }
executor.spawner().make_send()
}
/// Get a SendSpawner for this executor
///
/// This returns a [`SendSpawner`] you can use to spawn tasks on this
/// executor.
///
/// This MUST only be called on an executor that has already been started.
/// The function will panic otherwise.
pub fn spawner(&'static self) -> crate::SendSpawner {
if !critical_section::with(|cs| self.started.borrow(cs).get()) {
panic!("InterruptExecutor::spawner() called on uninitialized executor.");
}
let executor = unsafe { (&*self.executor.get()).assume_init_ref() };
executor.spawner().make_send()
}
}
}

View File

@ -0,0 +1,80 @@
#[cfg(feature = "executor-interrupt")]
compile_error!("`executor-interrupt` is not supported with `arch-riscv32`.");
#[cfg(feature = "executor-thread")]
pub use thread::*;
#[cfg(feature = "executor-thread")]
mod thread {
use core::marker::PhantomData;
use core::sync::atomic::{AtomicBool, Ordering};
pub use embassy_executor_macros::main_riscv as main;
use crate::{raw, Spawner};
/// global atomic used to keep track of whether there is work to do since sev() is not available on RISCV
static SIGNAL_WORK_THREAD_MODE: AtomicBool = AtomicBool::new(false);
#[export_name = "__pender"]
fn __pender(_context: *mut ()) {
SIGNAL_WORK_THREAD_MODE.store(true, Ordering::SeqCst);
}
/// RISCV32 Executor
pub struct Executor {
inner: raw::Executor,
not_send: PhantomData<*mut ()>,
}
impl Executor {
/// Create a new Executor.
pub fn new() -> Self {
Self {
inner: raw::Executor::new(core::ptr::null_mut()),
not_send: PhantomData,
}
}
/// Run the executor.
///
/// The `init` closure is called with a [`Spawner`] that spawns tasks on
/// this executor. Use it to spawn the initial task(s). After `init` returns,
/// the executor starts running the tasks.
///
/// To spawn more tasks later, you may keep copies of the [`Spawner`] (it is `Copy`),
/// for example by passing it as an argument to the initial tasks.
///
/// This function requires `&'static mut self`. This means you have to store the
/// Executor instance in a place where it'll live forever and grants you mutable
/// access. There's a few ways to do this:
///
/// - a [StaticCell](https://docs.rs/static_cell/latest/static_cell/) (safe)
/// - a `static mut` (unsafe)
/// - a local variable in a function you know never returns (like `fn main() -> !`), upgrading its lifetime with `transmute`. (unsafe)
///
/// This function never returns.
pub fn run(&'static mut self, init: impl FnOnce(Spawner)) -> ! {
init(self.inner.spawner());
loop {
unsafe {
self.inner.poll();
// we do not care about race conditions between the load and store operations, interrupts
//will only set this value to true.
critical_section::with(|_| {
// if there is work to do, loop back to polling
// TODO can we relax this?
if SIGNAL_WORK_THREAD_MODE.load(Ordering::SeqCst) {
SIGNAL_WORK_THREAD_MODE.store(false, Ordering::SeqCst);
}
// if not, wait for interrupt
else {
core::arch::asm!("wfi");
}
});
// if an interrupt occurred while waiting, it will be serviced here
}
}
}
}
}

View File

@ -0,0 +1,58 @@
#[cfg(feature = "executor-interrupt")]
compile_error!("`executor-interrupt` is not supported with `arch-spin`.");
#[cfg(feature = "executor-thread")]
pub use thread::*;
#[cfg(feature = "executor-thread")]
mod thread {
use core::marker::PhantomData;
pub use embassy_executor_macros::main_spin as main;
use crate::{raw, Spawner};
#[export_name = "__pender"]
fn __pender(_context: *mut ()) {}
/// Spin Executor
pub struct Executor {
inner: raw::Executor,
not_send: PhantomData<*mut ()>,
}
impl Executor {
/// Create a new Executor.
pub fn new() -> Self {
Self {
inner: raw::Executor::new(core::ptr::null_mut()),
not_send: PhantomData,
}
}
/// Run the executor.
///
/// The `init` closure is called with a [`Spawner`] that spawns tasks on
/// this executor. Use it to spawn the initial task(s). After `init` returns,
/// the executor starts running the tasks.
///
/// To spawn more tasks later, you may keep copies of the [`Spawner`] (it is `Copy`),
/// for example by passing it as an argument to the initial tasks.
///
/// This function requires `&'static mut self`. This means you have to store the
/// Executor instance in a place where it'll live forever and grants you mutable
/// access. There's a few ways to do this:
///
/// - a [StaticCell](https://docs.rs/static_cell/latest/static_cell/) (safe)
/// - a `static mut` (unsafe)
/// - a local variable in a function you know never returns (like `fn main() -> !`), upgrading its lifetime with `transmute`. (unsafe)
///
/// This function never returns.
pub fn run(&'static mut self, init: impl FnOnce(Spawner)) -> ! {
init(self.inner.spawner());
loop {
unsafe { self.inner.poll() };
}
}
}
}

View File

@ -0,0 +1,94 @@
#[cfg(feature = "executor-interrupt")]
compile_error!("`executor-interrupt` is not supported with `arch-std`.");
#[cfg(feature = "executor-thread")]
pub use thread::*;
#[cfg(feature = "executor-thread")]
mod thread {
use std::marker::PhantomData;
use std::sync::{Condvar, Mutex};
pub use embassy_executor_macros::main_std as main;
use crate::{raw, Spawner};
#[export_name = "__pender"]
fn __pender(context: *mut ()) {
let signaler: &'static Signaler = unsafe { std::mem::transmute(context) };
signaler.signal()
}
/// Single-threaded std-based executor.
pub struct Executor {
inner: raw::Executor,
not_send: PhantomData<*mut ()>,
signaler: &'static Signaler,
}
impl Executor {
/// Create a new Executor.
pub fn new() -> Self {
let signaler = Box::leak(Box::new(Signaler::new()));
Self {
inner: raw::Executor::new(signaler as *mut Signaler as *mut ()),
not_send: PhantomData,
signaler,
}
}
/// Run the executor.
///
/// The `init` closure is called with a [`Spawner`] that spawns tasks on
/// this executor. Use it to spawn the initial task(s). After `init` returns,
/// the executor starts running the tasks.
///
/// To spawn more tasks later, you may keep copies of the [`Spawner`] (it is `Copy`),
/// for example by passing it as an argument to the initial tasks.
///
/// This function requires `&'static mut self`. This means you have to store the
/// Executor instance in a place where it'll live forever and grants you mutable
/// access. There's a few ways to do this:
///
/// - a [StaticCell](https://docs.rs/static_cell/latest/static_cell/) (safe)
/// - a `static mut` (unsafe)
/// - a local variable in a function you know never returns (like `fn main() -> !`), upgrading its lifetime with `transmute`. (unsafe)
///
/// This function never returns.
pub fn run(&'static mut self, init: impl FnOnce(Spawner)) -> ! {
init(self.inner.spawner());
loop {
unsafe { self.inner.poll() };
self.signaler.wait()
}
}
}
struct Signaler {
mutex: Mutex<bool>,
condvar: Condvar,
}
impl Signaler {
fn new() -> Self {
Self {
mutex: Mutex::new(false),
condvar: Condvar::new(),
}
}
fn wait(&self) {
let mut signaled = self.mutex.lock().unwrap();
while !*signaled {
signaled = self.condvar.wait(signaled).unwrap();
}
*signaled = false;
}
fn signal(&self) {
let mut signaled = self.mutex.lock().unwrap();
*signaled = true;
self.condvar.notify_one();
}
}
}

View File

@ -0,0 +1,83 @@
#[cfg(feature = "executor-interrupt")]
compile_error!("`executor-interrupt` is not supported with `arch-wasm`.");
#[cfg(feature = "executor-thread")]
pub use thread::*;
#[cfg(feature = "executor-thread")]
mod thread {
use core::marker::PhantomData;
pub use embassy_executor_macros::main_wasm as main;
use js_sys::Promise;
use wasm_bindgen::prelude::*;
use crate::raw::util::UninitCell;
use crate::{raw, Spawner};
#[export_name = "__pender"]
fn __pender(context: *mut ()) {
let signaler: &'static WasmContext = unsafe { std::mem::transmute(context) };
let _ = signaler.promise.then(unsafe { signaler.closure.as_mut() });
}
pub(crate) struct WasmContext {
promise: Promise,
closure: UninitCell<Closure<dyn FnMut(JsValue)>>,
}
impl WasmContext {
pub fn new() -> Self {
Self {
promise: Promise::resolve(&JsValue::undefined()),
closure: UninitCell::uninit(),
}
}
}
/// WASM executor, wasm_bindgen to schedule tasks on the JS event loop.
pub struct Executor {
inner: raw::Executor,
ctx: &'static WasmContext,
not_send: PhantomData<*mut ()>,
}
impl Executor {
/// Create a new Executor.
pub fn new() -> Self {
let ctx = Box::leak(Box::new(WasmContext::new()));
Self {
inner: raw::Executor::new(ctx as *mut WasmContext as *mut ()),
ctx,
not_send: PhantomData,
}
}
/// Run the executor.
///
/// The `init` closure is called with a [`Spawner`] that spawns tasks on
/// this executor. Use it to spawn the initial task(s). After `init` returns,
/// the executor starts running the tasks.
///
/// To spawn more tasks later, you may keep copies of the [`Spawner`] (it is `Copy`),
/// for example by passing it as an argument to the initial tasks.
///
/// This function requires `&'static mut self`. This means you have to store the
/// Executor instance in a place where it'll live forever and grants you mutable
/// access. There's a few ways to do this:
///
/// - a [StaticCell](https://docs.rs/static_cell/latest/static_cell/) (safe)
/// - a `static mut` (unsafe)
/// - a local variable in a function you know never returns (like `fn main() -> !`), upgrading its lifetime with `transmute`. (unsafe)
pub fn start(&'static mut self, init: impl FnOnce(Spawner)) {
unsafe {
let executor = &self.inner;
let future = Closure::new(move |_| {
executor.poll();
});
self.ctx.closure.write_in_place(|| future);
init(self.inner.spawner());
}
}
}
}

270
embassy-executor/src/fmt.rs Normal file
View File

@ -0,0 +1,270 @@
#![macro_use]
#![allow(unused)]
use core::fmt::{Debug, Display, LowerHex};
#[cfg(all(feature = "defmt", feature = "log"))]
compile_error!("You may not enable both `defmt` and `log` features.");
#[collapse_debuginfo(yes)]
macro_rules! assert {
($($x:tt)*) => {
{
#[cfg(not(feature = "defmt"))]
::core::assert!($($x)*);
#[cfg(feature = "defmt")]
::defmt::assert!($($x)*);
}
};
}
#[collapse_debuginfo(yes)]
macro_rules! assert_eq {
($($x:tt)*) => {
{
#[cfg(not(feature = "defmt"))]
::core::assert_eq!($($x)*);
#[cfg(feature = "defmt")]
::defmt::assert_eq!($($x)*);
}
};
}
#[collapse_debuginfo(yes)]
macro_rules! assert_ne {
($($x:tt)*) => {
{
#[cfg(not(feature = "defmt"))]
::core::assert_ne!($($x)*);
#[cfg(feature = "defmt")]
::defmt::assert_ne!($($x)*);
}
};
}
#[collapse_debuginfo(yes)]
macro_rules! debug_assert {
($($x:tt)*) => {
{
#[cfg(not(feature = "defmt"))]
::core::debug_assert!($($x)*);
#[cfg(feature = "defmt")]
::defmt::debug_assert!($($x)*);
}
};
}
#[collapse_debuginfo(yes)]
macro_rules! debug_assert_eq {
($($x:tt)*) => {
{
#[cfg(not(feature = "defmt"))]
::core::debug_assert_eq!($($x)*);
#[cfg(feature = "defmt")]
::defmt::debug_assert_eq!($($x)*);
}
};
}
#[collapse_debuginfo(yes)]
macro_rules! debug_assert_ne {
($($x:tt)*) => {
{
#[cfg(not(feature = "defmt"))]
::core::debug_assert_ne!($($x)*);
#[cfg(feature = "defmt")]
::defmt::debug_assert_ne!($($x)*);
}
};
}
#[collapse_debuginfo(yes)]
macro_rules! todo {
($($x:tt)*) => {
{
#[cfg(not(feature = "defmt"))]
::core::todo!($($x)*);
#[cfg(feature = "defmt")]
::defmt::todo!($($x)*);
}
};
}
#[collapse_debuginfo(yes)]
macro_rules! unreachable {
($($x:tt)*) => {
{
#[cfg(not(feature = "defmt"))]
::core::unreachable!($($x)*);
#[cfg(feature = "defmt")]
::defmt::unreachable!($($x)*);
}
};
}
#[collapse_debuginfo(yes)]
macro_rules! panic {
($($x:tt)*) => {
{
#[cfg(not(feature = "defmt"))]
::core::panic!($($x)*);
#[cfg(feature = "defmt")]
::defmt::panic!($($x)*);
}
};
}
#[collapse_debuginfo(yes)]
macro_rules! trace {
($s:literal $(, $x:expr)* $(,)?) => {
{
#[cfg(feature = "log")]
::log::trace!($s $(, $x)*);
#[cfg(feature = "defmt")]
::defmt::trace!($s $(, $x)*);
#[cfg(not(any(feature = "log", feature="defmt")))]
let _ = ($( & $x ),*);
}
};
}
#[collapse_debuginfo(yes)]
macro_rules! debug {
($s:literal $(, $x:expr)* $(,)?) => {
{
#[cfg(feature = "log")]
::log::debug!($s $(, $x)*);
#[cfg(feature = "defmt")]
::defmt::debug!($s $(, $x)*);
#[cfg(not(any(feature = "log", feature="defmt")))]
let _ = ($( & $x ),*);
}
};
}
#[collapse_debuginfo(yes)]
macro_rules! info {
($s:literal $(, $x:expr)* $(,)?) => {
{
#[cfg(feature = "log")]
::log::info!($s $(, $x)*);
#[cfg(feature = "defmt")]
::defmt::info!($s $(, $x)*);
#[cfg(not(any(feature = "log", feature="defmt")))]
let _ = ($( & $x ),*);
}
};
}
#[collapse_debuginfo(yes)]
macro_rules! warn {
($s:literal $(, $x:expr)* $(,)?) => {
{
#[cfg(feature = "log")]
::log::warn!($s $(, $x)*);
#[cfg(feature = "defmt")]
::defmt::warn!($s $(, $x)*);
#[cfg(not(any(feature = "log", feature="defmt")))]
let _ = ($( & $x ),*);
}
};
}
#[collapse_debuginfo(yes)]
macro_rules! error {
($s:literal $(, $x:expr)* $(,)?) => {
{
#[cfg(feature = "log")]
::log::error!($s $(, $x)*);
#[cfg(feature = "defmt")]
::defmt::error!($s $(, $x)*);
#[cfg(not(any(feature = "log", feature="defmt")))]
let _ = ($( & $x ),*);
}
};
}
#[cfg(feature = "defmt")]
#[collapse_debuginfo(yes)]
macro_rules! unwrap {
($($x:tt)*) => {
::defmt::unwrap!($($x)*)
};
}
#[cfg(not(feature = "defmt"))]
#[collapse_debuginfo(yes)]
macro_rules! unwrap {
($arg:expr) => {
match $crate::fmt::Try::into_result($arg) {
::core::result::Result::Ok(t) => t,
::core::result::Result::Err(e) => {
::core::panic!("unwrap of `{}` failed: {:?}", ::core::stringify!($arg), e);
}
}
};
($arg:expr, $($msg:expr),+ $(,)? ) => {
match $crate::fmt::Try::into_result($arg) {
::core::result::Result::Ok(t) => t,
::core::result::Result::Err(e) => {
::core::panic!("unwrap of `{}` failed: {}: {:?}", ::core::stringify!($arg), ::core::format_args!($($msg,)*), e);
}
}
}
}
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub struct NoneError;
pub trait Try {
type Ok;
type Error;
fn into_result(self) -> Result<Self::Ok, Self::Error>;
}
impl<T> Try for Option<T> {
type Ok = T;
type Error = NoneError;
#[inline]
fn into_result(self) -> Result<T, NoneError> {
self.ok_or(NoneError)
}
}
impl<T, E> Try for Result<T, E> {
type Ok = T;
type Error = E;
#[inline]
fn into_result(self) -> Self {
self
}
}
pub(crate) 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)
}
}

150
embassy-executor/src/lib.rs Normal file
View File

@ -0,0 +1,150 @@
#![cfg_attr(not(any(feature = "arch-std", feature = "arch-wasm")), no_std)]
#![allow(clippy::new_without_default)]
#![doc = include_str!("../README.md")]
#![warn(missing_docs)]
//! ## Feature flags
#![doc = document_features::document_features!(feature_label = r#"<span class="stab portability"><code>{feature}</code></span>"#)]
// This mod MUST go first, so that the others see its macros.
pub(crate) mod fmt;
pub use embassy_executor_macros::task;
macro_rules! check_at_most_one {
(@amo [$($feats:literal)*] [] [$($res:tt)*]) => {
#[cfg(any($($res)*))]
compile_error!(concat!("At most one of these features can be enabled at the same time:", $(" `", $feats, "`",)*));
};
(@amo $feats:tt [$curr:literal $($rest:literal)*] [$($res:tt)*]) => {
check_at_most_one!(@amo $feats [$($rest)*] [$($res)* $(all(feature=$curr, feature=$rest),)*]);
};
($($f:literal),*$(,)?) => {
check_at_most_one!(@amo [$($f)*] [$($f)*] []);
};
}
check_at_most_one!(
"arch-avr",
"arch-cortex-m",
"arch-riscv32",
"arch-std",
"arch-wasm",
"arch-spin",
);
#[cfg(feature = "_arch")]
#[cfg_attr(feature = "arch-avr", path = "arch/avr.rs")]
#[cfg_attr(feature = "arch-cortex-m", path = "arch/cortex_m.rs")]
#[cfg_attr(feature = "arch-riscv32", path = "arch/riscv32.rs")]
#[cfg_attr(feature = "arch-std", path = "arch/std.rs")]
#[cfg_attr(feature = "arch-wasm", path = "arch/wasm.rs")]
#[cfg_attr(feature = "arch-spin", path = "arch/spin.rs")]
mod arch;
#[cfg(feature = "_arch")]
#[allow(unused_imports)] // don't warn if the module is empty.
pub use arch::*;
pub mod raw;
mod spawner;
pub use spawner::*;
mod config {
#![allow(unused)]
include!(concat!(env!("OUT_DIR"), "/config.rs"));
}
/// Implementation details for embassy macros.
/// Do not use. Used for macros and HALs only. Not covered by semver guarantees.
#[doc(hidden)]
#[cfg(not(feature = "nightly"))]
pub mod _export {
use core::alloc::Layout;
use core::cell::{Cell, UnsafeCell};
use core::future::Future;
use core::mem::MaybeUninit;
use core::ptr::null_mut;
use critical_section::{CriticalSection, Mutex};
use crate::raw::TaskPool;
struct Arena<const N: usize> {
buf: UnsafeCell<MaybeUninit<[u8; N]>>,
ptr: Mutex<Cell<*mut u8>>,
}
unsafe impl<const N: usize> Sync for Arena<N> {}
unsafe impl<const N: usize> Send for Arena<N> {}
impl<const N: usize> Arena<N> {
const fn new() -> Self {
Self {
buf: UnsafeCell::new(MaybeUninit::uninit()),
ptr: Mutex::new(Cell::new(null_mut())),
}
}
fn alloc<T>(&'static self, cs: CriticalSection) -> &'static mut MaybeUninit<T> {
let layout = Layout::new::<T>();
let start = self.buf.get().cast::<u8>();
let end = unsafe { start.add(N) };
let mut ptr = self.ptr.borrow(cs).get();
if ptr.is_null() {
ptr = self.buf.get().cast::<u8>();
}
let bytes_left = (end as usize) - (ptr as usize);
let align_offset = (ptr as usize).next_multiple_of(layout.align()) - (ptr as usize);
if align_offset + layout.size() > bytes_left {
panic!("embassy-executor: task arena is full. You must increase the arena size, see the documentation for details: https://docs.embassy.dev/embassy-executor/");
}
let res = unsafe { ptr.add(align_offset) };
let ptr = unsafe { ptr.add(align_offset + layout.size()) };
self.ptr.borrow(cs).set(ptr);
unsafe { &mut *(res as *mut MaybeUninit<T>) }
}
}
static ARENA: Arena<{ crate::config::TASK_ARENA_SIZE }> = Arena::new();
pub struct TaskPoolRef {
// type-erased `&'static mut TaskPool<F, N>`
// Needed because statics can't have generics.
ptr: Mutex<Cell<*mut ()>>,
}
unsafe impl Sync for TaskPoolRef {}
unsafe impl Send for TaskPoolRef {}
impl TaskPoolRef {
pub const fn new() -> Self {
Self {
ptr: Mutex::new(Cell::new(null_mut())),
}
}
/// Get the pool for this ref, allocating it from the arena the first time.
///
/// safety: for a given TaskPoolRef instance, must always call with the exact
/// same generic params.
pub unsafe fn get<F: Future, const N: usize>(&'static self) -> &'static TaskPool<F, N> {
critical_section::with(|cs| {
let ptr = self.ptr.borrow(cs);
if ptr.get().is_null() {
let pool = ARENA.alloc::<TaskPool<F, N>>(cs);
pool.write(TaskPool::new());
ptr.set(pool as *mut _ as _);
}
unsafe { &*(ptr.get() as *const _) }
})
}
}
}

View File

@ -0,0 +1,571 @@
//! Raw executor.
//!
//! This module exposes "raw" Executor and Task structs for more low level control.
//!
//! ## WARNING: here be dragons!
//!
//! Using this module requires respecting subtle safety contracts. If you can, prefer using the safe
//! [executor wrappers](crate::Executor) and the [`embassy_executor::task`](embassy_executor_macros::task) macro, which are fully safe.
#[cfg_attr(target_has_atomic = "ptr", path = "run_queue_atomics.rs")]
#[cfg_attr(not(target_has_atomic = "ptr"), path = "run_queue_critical_section.rs")]
mod run_queue;
#[cfg_attr(all(cortex_m, target_has_atomic = "8"), path = "state_atomics_arm.rs")]
#[cfg_attr(all(not(cortex_m), target_has_atomic = "8"), path = "state_atomics.rs")]
#[cfg_attr(not(target_has_atomic = "8"), path = "state_critical_section.rs")]
mod state;
pub mod timer_queue;
#[cfg(feature = "trace")]
mod trace;
pub(crate) mod util;
#[cfg_attr(feature = "turbowakers", path = "waker_turbo.rs")]
mod waker;
use core::future::Future;
use core::marker::PhantomData;
use core::mem;
use core::pin::Pin;
use core::ptr::NonNull;
use core::sync::atomic::{AtomicPtr, Ordering};
use core::task::{Context, Poll};
use self::run_queue::{RunQueue, RunQueueItem};
use self::state::State;
use self::util::{SyncUnsafeCell, UninitCell};
pub use self::waker::task_from_waker;
use super::SpawnToken;
/// Raw task header for use in task pointers.
///
/// A task can be in one of the following states:
///
/// - Not spawned: the task is ready to spawn.
/// - `SPAWNED`: the task is currently spawned and may be running.
/// - `RUN_ENQUEUED`: the task is enqueued to be polled. Note that the task may be `!SPAWNED`.
/// In this case, the `RUN_ENQUEUED` state will be cleared when the task is next polled, without
/// polling the task's future.
///
/// A task's complete life cycle is as follows:
///
/// ```text
/// ┌────────────┐ ┌────────────────────────┐
/// │Not spawned │◄─5┤Not spawned|Run enqueued│
/// │ ├6─►│ │
/// └─────┬──────┘ └──────▲─────────────────┘
/// 1 │
/// │ ┌────────────┘
/// │ 4
/// ┌─────▼────┴─────────┐
/// │Spawned|Run enqueued│
/// │ │
/// └─────┬▲─────────────┘
/// 2│
/// │3
/// ┌─────▼┴─────┐
/// │ Spawned │
/// │ │
/// └────────────┘
/// ```
///
/// Transitions:
/// - 1: Task is spawned - `AvailableTask::claim -> Executor::spawn`
/// - 2: During poll - `RunQueue::dequeue_all -> State::run_dequeue`
/// - 3: Task wakes itself, waker wakes task, or task exits - `Waker::wake -> wake_task -> State::run_enqueue`
/// - 4: A run-queued task exits - `TaskStorage::poll -> Poll::Ready`
/// - 5: Task is dequeued. The task's future is not polled, because exiting the task replaces its `poll_fn`.
/// - 6: A task is waken when it is not spawned - `wake_task -> State::run_enqueue`
pub(crate) struct TaskHeader {
pub(crate) state: State,
pub(crate) run_queue_item: RunQueueItem,
pub(crate) executor: AtomicPtr<SyncExecutor>,
poll_fn: SyncUnsafeCell<Option<unsafe fn(TaskRef)>>,
/// Integrated timer queue storage. This field should not be accessed outside of the timer queue.
pub(crate) timer_queue_item: timer_queue::TimerQueueItem,
}
/// This is essentially a `&'static TaskStorage<F>` where the type of the future has been erased.
#[derive(Clone, Copy, PartialEq)]
pub struct TaskRef {
ptr: NonNull<TaskHeader>,
}
unsafe impl Send for TaskRef where &'static TaskHeader: Send {}
unsafe impl Sync for TaskRef where &'static TaskHeader: Sync {}
impl TaskRef {
fn new<F: Future + 'static>(task: &'static TaskStorage<F>) -> Self {
Self {
ptr: NonNull::from(task).cast(),
}
}
/// Safety: The pointer must have been obtained with `Task::as_ptr`
pub(crate) unsafe fn from_ptr(ptr: *const TaskHeader) -> Self {
Self {
ptr: NonNull::new_unchecked(ptr as *mut TaskHeader),
}
}
/// # Safety
///
/// The result of this function must only be compared
/// for equality, or stored, but not used.
pub const unsafe fn dangling() -> Self {
Self {
ptr: NonNull::dangling(),
}
}
pub(crate) fn header(self) -> &'static TaskHeader {
unsafe { self.ptr.as_ref() }
}
/// Returns a reference to the executor that the task is currently running on.
pub unsafe fn executor(self) -> Option<&'static Executor> {
let executor = self.header().executor.load(Ordering::Relaxed);
executor.as_ref().map(|e| Executor::wrap(e))
}
/// Returns a reference to the timer queue item.
pub fn timer_queue_item(&self) -> &'static timer_queue::TimerQueueItem {
&self.header().timer_queue_item
}
/// The returned pointer is valid for the entire TaskStorage.
pub(crate) fn as_ptr(self) -> *const TaskHeader {
self.ptr.as_ptr()
}
}
/// Raw storage in which a task can be spawned.
///
/// This struct holds the necessary memory to spawn one task whose future is `F`.
/// At a given time, the `TaskStorage` may be in spawned or not-spawned state. You
/// may spawn it with [`TaskStorage::spawn()`], which will fail if it is already spawned.
///
/// A `TaskStorage` must live forever, it may not be deallocated even after the task has finished
/// running. Hence the relevant methods require `&'static self`. It may be reused, however.
///
/// Internally, the [embassy_executor::task](embassy_executor_macros::task) macro allocates an array of `TaskStorage`s
/// in a `static`. The most common reason to use the raw `Task` is to have control of where
/// the memory for the task is allocated: on the stack, or on the heap with e.g. `Box::leak`, etc.
// repr(C) is needed to guarantee that the Task is located at offset 0
// This makes it safe to cast between TaskHeader and TaskStorage pointers.
#[repr(C)]
pub struct TaskStorage<F: Future + 'static> {
raw: TaskHeader,
future: UninitCell<F>, // Valid if STATE_SPAWNED
}
unsafe fn poll_exited(_p: TaskRef) {
// Nothing to do, the task is already !SPAWNED and dequeued.
}
impl<F: Future + 'static> TaskStorage<F> {
const NEW: Self = Self::new();
/// Create a new TaskStorage, in not-spawned state.
pub const fn new() -> Self {
Self {
raw: TaskHeader {
state: State::new(),
run_queue_item: RunQueueItem::new(),
executor: AtomicPtr::new(core::ptr::null_mut()),
// Note: this is lazily initialized so that a static `TaskStorage` will go in `.bss`
poll_fn: SyncUnsafeCell::new(None),
timer_queue_item: timer_queue::TimerQueueItem::new(),
},
future: UninitCell::uninit(),
}
}
/// Try to spawn the task.
///
/// The `future` closure constructs the future. It's only called if spawning is
/// actually possible. It is a closure instead of a simple `future: F` param to ensure
/// the future is constructed in-place, avoiding a temporary copy in the stack thanks to
/// NRVO optimizations.
///
/// This function will fail if the task is already spawned and has not finished running.
/// In this case, the error is delayed: a "poisoned" SpawnToken is returned, which will
/// cause [`Spawner::spawn()`](super::Spawner::spawn) to return the error.
///
/// Once the task has finished running, you may spawn it again. It is allowed to spawn it
/// on a different executor.
pub fn spawn(&'static self, future: impl FnOnce() -> F) -> SpawnToken<impl Sized> {
let task = AvailableTask::claim(self);
match task {
Some(task) => task.initialize(future),
None => SpawnToken::new_failed(),
}
}
unsafe fn poll(p: TaskRef) {
let this = &*p.as_ptr().cast::<TaskStorage<F>>();
let future = Pin::new_unchecked(this.future.as_mut());
let waker = waker::from_task(p);
let mut cx = Context::from_waker(&waker);
match future.poll(&mut cx) {
Poll::Ready(_) => {
// As the future has finished and this function will not be called
// again, we can safely drop the future here.
this.future.drop_in_place();
// We replace the poll_fn with a despawn function, so that the task is cleaned up
// when the executor polls it next.
this.raw.poll_fn.set(Some(poll_exited));
// Make sure we despawn last, so that other threads can only spawn the task
// after we're done with it.
this.raw.state.despawn();
}
Poll::Pending => {}
}
// the compiler is emitting a virtual call for waker drop, but we know
// it's a noop for our waker.
mem::forget(waker);
}
#[doc(hidden)]
#[allow(dead_code)]
fn _assert_sync(self) {
fn assert_sync<T: Sync>(_: T) {}
assert_sync(self)
}
}
/// An uninitialized [`TaskStorage`].
pub struct AvailableTask<F: Future + 'static> {
task: &'static TaskStorage<F>,
}
impl<F: Future + 'static> AvailableTask<F> {
/// Try to claim a [`TaskStorage`].
///
/// This function returns `None` if a task has already been spawned and has not finished running.
pub fn claim(task: &'static TaskStorage<F>) -> Option<Self> {
task.raw.state.spawn().then(|| Self { task })
}
fn initialize_impl<S>(self, future: impl FnOnce() -> F) -> SpawnToken<S> {
unsafe {
self.task.raw.poll_fn.set(Some(TaskStorage::<F>::poll));
self.task.future.write_in_place(future);
let task = TaskRef::new(self.task);
SpawnToken::new(task)
}
}
/// Initialize the [`TaskStorage`] to run the given future.
pub fn initialize(self, future: impl FnOnce() -> F) -> SpawnToken<F> {
self.initialize_impl::<F>(future)
}
/// Initialize the [`TaskStorage`] to run the given future.
///
/// # Safety
///
/// `future` must be a closure of the form `move || my_async_fn(args)`, where `my_async_fn`
/// is an `async fn`, NOT a hand-written `Future`.
#[doc(hidden)]
pub unsafe fn __initialize_async_fn<FutFn>(self, future: impl FnOnce() -> F) -> SpawnToken<FutFn> {
// When send-spawning a task, we construct the future in this thread, and effectively
// "send" it to the executor thread by enqueuing it in its queue. Therefore, in theory,
// send-spawning should require the future `F` to be `Send`.
//
// The problem is this is more restrictive than needed. Once the future is executing,
// it is never sent to another thread. It is only sent when spawning. It should be
// enough for the task's arguments to be Send. (and in practice it's super easy to
// accidentally make your futures !Send, for example by holding an `Rc` or a `&RefCell` across an `.await`.)
//
// We can do it by sending the task args and constructing the future in the executor thread
// on first poll. However, this cannot be done in-place, so it'll waste stack space for a copy
// of the args.
//
// Luckily, an `async fn` future contains just the args when freshly constructed. So, if the
// args are Send, it's OK to send a !Send future, as long as we do it before first polling it.
//
// (Note: this is how the generators are implemented today, it's not officially guaranteed yet,
// but it's possible it'll be guaranteed in the future. See zulip thread:
// https://rust-lang.zulipchat.com/#narrow/stream/187312-wg-async/topic/.22only.20before.20poll.22.20Send.20futures )
//
// The `FutFn` captures all the args, so if it's Send, the task can be send-spawned.
// This is why we return `SpawnToken<FutFn>` below.
//
// This ONLY holds for `async fn` futures. The other `spawn` methods can be called directly
// by the user, with arbitrary hand-implemented futures. This is why these return `SpawnToken<F>`.
self.initialize_impl::<FutFn>(future)
}
}
/// Raw storage that can hold up to N tasks of the same type.
///
/// This is essentially a `[TaskStorage<F>; N]`.
pub struct TaskPool<F: Future + 'static, const N: usize> {
pool: [TaskStorage<F>; N],
}
impl<F: Future + 'static, const N: usize> TaskPool<F, N> {
/// Create a new TaskPool, with all tasks in non-spawned state.
pub const fn new() -> Self {
Self {
pool: [TaskStorage::NEW; N],
}
}
fn spawn_impl<T>(&'static self, future: impl FnOnce() -> F) -> SpawnToken<T> {
match self.pool.iter().find_map(AvailableTask::claim) {
Some(task) => task.initialize_impl::<T>(future),
None => SpawnToken::new_failed(),
}
}
/// Try to spawn a task in the pool.
///
/// See [`TaskStorage::spawn()`] for details.
///
/// This will loop over the pool and spawn the task in the first storage that
/// is currently free. If none is free, a "poisoned" SpawnToken is returned,
/// which will cause [`Spawner::spawn()`](super::Spawner::spawn) to return the error.
pub fn spawn(&'static self, future: impl FnOnce() -> F) -> SpawnToken<impl Sized> {
self.spawn_impl::<F>(future)
}
/// Like spawn(), but allows the task to be send-spawned if the args are Send even if
/// the future is !Send.
///
/// Not covered by semver guarantees. DO NOT call this directly. Intended to be used
/// by the Embassy macros ONLY.
///
/// SAFETY: `future` must be a closure of the form `move || my_async_fn(args)`, where `my_async_fn`
/// is an `async fn`, NOT a hand-written `Future`.
#[doc(hidden)]
pub unsafe fn _spawn_async_fn<FutFn>(&'static self, future: FutFn) -> SpawnToken<impl Sized>
where
FutFn: FnOnce() -> F,
{
// See the comment in AvailableTask::__initialize_async_fn for explanation.
self.spawn_impl::<FutFn>(future)
}
}
#[derive(Clone, Copy)]
pub(crate) struct Pender(*mut ());
unsafe impl Send for Pender {}
unsafe impl Sync for Pender {}
impl Pender {
pub(crate) fn pend(self) {
extern "Rust" {
fn __pender(context: *mut ());
}
unsafe { __pender(self.0) };
}
}
pub(crate) struct SyncExecutor {
run_queue: RunQueue,
pender: Pender,
}
impl SyncExecutor {
pub(crate) fn new(pender: Pender) -> Self {
Self {
run_queue: RunQueue::new(),
pender,
}
}
/// Enqueue a task in the task queue
///
/// # Safety
/// - `task` must be a valid pointer to a spawned task.
/// - `task` must be set up to run in this executor.
/// - `task` must NOT be already enqueued (in this executor or another one).
#[inline(always)]
unsafe fn enqueue(&self, task: TaskRef, l: state::Token) {
#[cfg(feature = "trace")]
trace::task_ready_begin(self, &task);
if self.run_queue.enqueue(task, l) {
self.pender.pend();
}
}
pub(super) unsafe fn spawn(&'static self, task: TaskRef) {
task.header()
.executor
.store((self as *const Self).cast_mut(), Ordering::Relaxed);
#[cfg(feature = "trace")]
trace::task_new(self, &task);
state::locked(|l| {
self.enqueue(task, l);
})
}
/// # Safety
///
/// Same as [`Executor::poll`], plus you must only call this on the thread this executor was created.
pub(crate) unsafe fn poll(&'static self) {
self.run_queue.dequeue_all(|p| {
let task = p.header();
#[cfg(feature = "trace")]
trace::task_exec_begin(self, &p);
// Run the task
task.poll_fn.get().unwrap_unchecked()(p);
#[cfg(feature = "trace")]
trace::task_exec_end(self, &p);
});
#[cfg(feature = "trace")]
trace::executor_idle(self)
}
}
/// Raw executor.
///
/// This is the core of the Embassy executor. It is low-level, requiring manual
/// handling of wakeups and task polling. If you can, prefer using one of the
/// [higher level executors](crate::Executor).
///
/// The raw executor leaves it up to you to handle wakeups and scheduling:
///
/// - To get the executor to do work, call `poll()`. This will poll all queued tasks (all tasks
/// that "want to run").
/// - You must supply a pender function, as shown below. The executor will call it to notify you
/// it has work to do. You must arrange for `poll()` to be called as soon as possible.
/// - Enabling `arch-xx` features will define a pender function for you. This means that you
/// are limited to using the executors provided to you by the architecture/platform
/// implementation. If you need a different executor, you must not enable `arch-xx` features.
///
/// The pender can be called from *any* context: any thread, any interrupt priority
/// level, etc. It may be called synchronously from any `Executor` method call as well.
/// You must deal with this correctly.
///
/// In particular, you must NOT call `poll` directly from the pender callback, as this violates
/// the requirement for `poll` to not be called reentrantly.
///
/// The pender function must be exported with the name `__pender` and have the following signature:
///
/// ```rust
/// #[export_name = "__pender"]
/// fn pender(context: *mut ()) {
/// // schedule `poll()` to be called
/// }
/// ```
///
/// The `context` argument is a piece of arbitrary data the executor will pass to the pender.
/// You can set the `context` when calling [`Executor::new()`]. You can use it to, for example,
/// differentiate between executors, or to pass a pointer to a callback that should be called.
#[repr(transparent)]
pub struct Executor {
pub(crate) inner: SyncExecutor,
_not_sync: PhantomData<*mut ()>,
}
impl Executor {
pub(crate) unsafe fn wrap(inner: &SyncExecutor) -> &Self {
mem::transmute(inner)
}
/// Create a new executor.
///
/// When the executor has work to do, it will call the pender function and pass `context` to it.
///
/// See [`Executor`] docs for details on the pender.
pub fn new(context: *mut ()) -> Self {
Self {
inner: SyncExecutor::new(Pender(context)),
_not_sync: PhantomData,
}
}
/// Spawn a task in this executor.
///
/// # Safety
///
/// `task` must be a valid pointer to an initialized but not-already-spawned task.
///
/// It is OK to use `unsafe` to call this from a thread that's not the executor thread.
/// In this case, the task's Future must be Send. This is because this is effectively
/// sending the task to the executor thread.
pub(super) unsafe fn spawn(&'static self, task: TaskRef) {
self.inner.spawn(task)
}
/// Poll all queued tasks in this executor.
///
/// This loops over all tasks that are queued to be polled (i.e. they're
/// freshly spawned or they've been woken). Other tasks are not polled.
///
/// You must call `poll` after receiving a call to the pender. It is OK
/// to call `poll` even when not requested by the pender, but it wastes
/// energy.
///
/// # Safety
///
/// You must call `initialize` before calling this method.
///
/// You must NOT call `poll` reentrantly on the same executor.
///
/// In particular, note that `poll` may call the pender synchronously. Therefore, you
/// must NOT directly call `poll()` from the pender callback. Instead, the callback has to
/// somehow schedule for `poll()` to be called later, at a time you know for sure there's
/// no `poll()` already running.
pub unsafe fn poll(&'static self) {
self.inner.poll()
}
/// Get a spawner that spawns tasks in this executor.
///
/// It is OK to call this method multiple times to obtain multiple
/// `Spawner`s. You may also copy `Spawner`s.
pub fn spawner(&'static self) -> super::Spawner {
super::Spawner::new(self)
}
}
/// Wake a task by `TaskRef`.
///
/// You can obtain a `TaskRef` from a `Waker` using [`task_from_waker`].
pub fn wake_task(task: TaskRef) {
let header = task.header();
header.state.run_enqueue(|l| {
// We have just marked the task as scheduled, so enqueue it.
unsafe {
let executor = header.executor.load(Ordering::Relaxed).as_ref().unwrap_unchecked();
executor.enqueue(task, l);
}
});
}
/// Wake a task by `TaskRef` without calling pend.
///
/// You can obtain a `TaskRef` from a `Waker` using [`task_from_waker`].
pub fn wake_task_no_pend(task: TaskRef) {
let header = task.header();
header.state.run_enqueue(|l| {
// We have just marked the task as scheduled, so enqueue it.
unsafe {
let executor = header.executor.load(Ordering::Relaxed).as_ref().unwrap_unchecked();
executor.run_queue.enqueue(task, l);
}
});
}

View File

@ -0,0 +1,88 @@
use core::ptr;
use core::ptr::NonNull;
use core::sync::atomic::{AtomicPtr, Ordering};
use super::{TaskHeader, TaskRef};
use crate::raw::util::SyncUnsafeCell;
pub(crate) struct RunQueueItem {
next: SyncUnsafeCell<Option<TaskRef>>,
}
impl RunQueueItem {
pub const fn new() -> Self {
Self {
next: SyncUnsafeCell::new(None),
}
}
}
/// Atomic task queue using a very, very simple lock-free linked-list queue:
///
/// To enqueue a task, task.next is set to the old head, and head is atomically set to task.
///
/// Dequeuing is done in batches: the queue is emptied by atomically replacing head with
/// null. Then the batch is iterated following the next pointers until null is reached.
///
/// Note that batches will be iterated in the reverse order as they were enqueued. This is OK
/// for our purposes: it can't create fairness problems since the next batch won't run until the
/// current batch is completely processed, so even if a task enqueues itself instantly (for example
/// by waking its own waker) can't prevent other tasks from running.
pub(crate) struct RunQueue {
head: AtomicPtr<TaskHeader>,
}
impl RunQueue {
pub const fn new() -> Self {
Self {
head: AtomicPtr::new(ptr::null_mut()),
}
}
/// Enqueues an item. Returns true if the queue was empty.
///
/// # Safety
///
/// `item` must NOT be already enqueued in any queue.
#[inline(always)]
pub(crate) unsafe fn enqueue(&self, task: TaskRef, _: super::state::Token) -> bool {
let mut was_empty = false;
self.head
.fetch_update(Ordering::SeqCst, Ordering::SeqCst, |prev| {
was_empty = prev.is_null();
unsafe {
// safety: the pointer is either null or valid
let prev = NonNull::new(prev).map(|ptr| TaskRef::from_ptr(ptr.as_ptr()));
// safety: there are no concurrent accesses to `next`
task.header().run_queue_item.next.set(prev);
}
Some(task.as_ptr() as *mut _)
})
.ok();
was_empty
}
/// Empty the queue, then call `on_task` for each task that was in the queue.
/// NOTE: It is OK for `on_task` to enqueue more tasks. In this case they're left in the queue
/// and will be processed by the *next* call to `dequeue_all`, *not* the current one.
pub(crate) fn dequeue_all(&self, on_task: impl Fn(TaskRef)) {
// Atomically empty the queue.
let ptr = self.head.swap(ptr::null_mut(), Ordering::AcqRel);
// safety: the pointer is either null or valid
let mut next = unsafe { NonNull::new(ptr).map(|ptr| TaskRef::from_ptr(ptr.as_ptr())) };
// Iterate the linked list of tasks that were previously in the queue.
while let Some(task) = next {
// If the task re-enqueues itself, the `next` pointer will get overwritten.
// Therefore, first read the next pointer, and only then process the task.
// safety: there are no concurrent accesses to `next`
next = unsafe { task.header().run_queue_item.next.get() };
task.header().state.run_dequeue();
on_task(task);
}
}
}

View File

@ -0,0 +1,74 @@
use core::cell::Cell;
use critical_section::{CriticalSection, Mutex};
use super::TaskRef;
pub(crate) struct RunQueueItem {
next: Mutex<Cell<Option<TaskRef>>>,
}
impl RunQueueItem {
pub const fn new() -> Self {
Self {
next: Mutex::new(Cell::new(None)),
}
}
}
/// Atomic task queue using a very, very simple lock-free linked-list queue:
///
/// To enqueue a task, task.next is set to the old head, and head is atomically set to task.
///
/// Dequeuing is done in batches: the queue is emptied by atomically replacing head with
/// null. Then the batch is iterated following the next pointers until null is reached.
///
/// Note that batches will be iterated in the reverse order as they were enqueued. This is OK
/// for our purposes: it can't create fairness problems since the next batch won't run until the
/// current batch is completely processed, so even if a task enqueues itself instantly (for example
/// by waking its own waker) can't prevent other tasks from running.
pub(crate) struct RunQueue {
head: Mutex<Cell<Option<TaskRef>>>,
}
impl RunQueue {
pub const fn new() -> Self {
Self {
head: Mutex::new(Cell::new(None)),
}
}
/// Enqueues an item. Returns true if the queue was empty.
///
/// # Safety
///
/// `item` must NOT be already enqueued in any queue.
#[inline(always)]
pub(crate) unsafe fn enqueue(&self, task: TaskRef, cs: CriticalSection<'_>) -> bool {
let prev = self.head.borrow(cs).replace(Some(task));
task.header().run_queue_item.next.borrow(cs).set(prev);
prev.is_none()
}
/// Empty the queue, then call `on_task` for each task that was in the queue.
/// NOTE: It is OK for `on_task` to enqueue more tasks. In this case they're left in the queue
/// and will be processed by the *next* call to `dequeue_all`, *not* the current one.
pub(crate) fn dequeue_all(&self, on_task: impl Fn(TaskRef)) {
// Atomically empty the queue.
let mut next = critical_section::with(|cs| self.head.borrow(cs).take());
// Iterate the linked list of tasks that were previously in the queue.
while let Some(task) = next {
// If the task re-enqueues itself, the `next` pointer will get overwritten.
// Therefore, first read the next pointer, and only then process the task.
critical_section::with(|cs| {
next = task.header().run_queue_item.next.borrow(cs).get();
task.header().state.run_dequeue(cs);
});
on_task(task);
}
}
}

View File

@ -0,0 +1,58 @@
use core::sync::atomic::{AtomicU32, Ordering};
#[derive(Clone, Copy)]
pub(crate) struct Token(());
/// Creates a token and passes it to the closure.
///
/// This is a no-op replacement for `CriticalSection::with` because we don't need any locking.
pub(crate) fn locked<R>(f: impl FnOnce(Token) -> R) -> R {
f(Token(()))
}
/// Task is spawned (has a future)
pub(crate) const STATE_SPAWNED: u32 = 1 << 0;
/// Task is in the executor run queue
pub(crate) const STATE_RUN_QUEUED: u32 = 1 << 1;
pub(crate) struct State {
state: AtomicU32,
}
impl State {
pub const fn new() -> State {
Self {
state: AtomicU32::new(0),
}
}
/// If task is idle, mark it as spawned + run_queued and return true.
#[inline(always)]
pub fn spawn(&self) -> bool {
self.state
.compare_exchange(0, STATE_SPAWNED | STATE_RUN_QUEUED, Ordering::AcqRel, Ordering::Acquire)
.is_ok()
}
/// Unmark the task as spawned.
#[inline(always)]
pub fn despawn(&self) {
self.state.fetch_and(!STATE_SPAWNED, Ordering::AcqRel);
}
/// Mark the task as run-queued if it's spawned and isn't already run-queued. Run the given
/// function if the task was successfully marked.
#[inline(always)]
pub fn run_enqueue(&self, f: impl FnOnce(Token)) {
let prev = self.state.fetch_or(STATE_RUN_QUEUED, Ordering::AcqRel);
if prev & STATE_RUN_QUEUED == 0 {
locked(f);
}
}
/// Unmark the task as run-queued. Return whether the task is spawned.
#[inline(always)]
pub fn run_dequeue(&self) {
self.state.fetch_and(!STATE_RUN_QUEUED, Ordering::AcqRel);
}
}

View File

@ -0,0 +1,83 @@
use core::sync::atomic::{compiler_fence, AtomicBool, AtomicU32, Ordering};
#[derive(Clone, Copy)]
pub(crate) struct Token(());
/// Creates a token and passes it to the closure.
///
/// This is a no-op replacement for `CriticalSection::with` because we don't need any locking.
pub(crate) fn locked<R>(f: impl FnOnce(Token) -> R) -> R {
f(Token(()))
}
// Must be kept in sync with the layout of `State`!
pub(crate) const STATE_SPAWNED: u32 = 1 << 0;
pub(crate) const STATE_RUN_QUEUED: u32 = 1 << 8;
#[repr(C, align(4))]
pub(crate) struct State {
/// Task is spawned (has a future)
spawned: AtomicBool,
/// Task is in the executor run queue
run_queued: AtomicBool,
pad: AtomicBool,
pad2: AtomicBool,
}
impl State {
pub const fn new() -> State {
Self {
spawned: AtomicBool::new(false),
run_queued: AtomicBool::new(false),
pad: AtomicBool::new(false),
pad2: AtomicBool::new(false),
}
}
fn as_u32(&self) -> &AtomicU32 {
unsafe { &*(self as *const _ as *const AtomicU32) }
}
/// If task is idle, mark it as spawned + run_queued and return true.
#[inline(always)]
pub fn spawn(&self) -> bool {
compiler_fence(Ordering::Release);
let r = self
.as_u32()
.compare_exchange(
0,
STATE_SPAWNED | STATE_RUN_QUEUED,
Ordering::Relaxed,
Ordering::Relaxed,
)
.is_ok();
compiler_fence(Ordering::Acquire);
r
}
/// Unmark the task as spawned.
#[inline(always)]
pub fn despawn(&self) {
compiler_fence(Ordering::Release);
self.spawned.store(false, Ordering::Relaxed);
}
/// Mark the task as run-queued if it's spawned and isn't already run-queued. Run the given
/// function if the task was successfully marked.
#[inline(always)]
pub fn run_enqueue(&self, f: impl FnOnce(Token)) {
let old = self.run_queued.swap(true, Ordering::AcqRel);
if !old {
locked(f);
}
}
/// Unmark the task as run-queued. Return whether the task is spawned.
#[inline(always)]
pub fn run_dequeue(&self) {
compiler_fence(Ordering::Release);
self.run_queued.store(false, Ordering::Relaxed);
}
}

View File

@ -0,0 +1,73 @@
use core::cell::Cell;
pub(crate) use critical_section::{with as locked, CriticalSection as Token};
use critical_section::{CriticalSection, Mutex};
/// Task is spawned (has a future)
pub(crate) const STATE_SPAWNED: u32 = 1 << 0;
/// Task is in the executor run queue
pub(crate) const STATE_RUN_QUEUED: u32 = 1 << 1;
pub(crate) struct State {
state: Mutex<Cell<u32>>,
}
impl State {
pub const fn new() -> State {
Self {
state: Mutex::new(Cell::new(0)),
}
}
fn update<R>(&self, f: impl FnOnce(&mut u32) -> R) -> R {
critical_section::with(|cs| self.update_with_cs(cs, f))
}
fn update_with_cs<R>(&self, cs: CriticalSection<'_>, f: impl FnOnce(&mut u32) -> R) -> R {
let s = self.state.borrow(cs);
let mut val = s.get();
let r = f(&mut val);
s.set(val);
r
}
/// If task is idle, mark it as spawned + run_queued and return true.
#[inline(always)]
pub fn spawn(&self) -> bool {
self.update(|s| {
if *s == 0 {
*s = STATE_SPAWNED | STATE_RUN_QUEUED;
true
} else {
false
}
})
}
/// Unmark the task as spawned.
#[inline(always)]
pub fn despawn(&self) {
self.update(|s| *s &= !STATE_SPAWNED);
}
/// Mark the task as run-queued if it's spawned and isn't already run-queued. Run the given
/// function if the task was successfully marked.
#[inline(always)]
pub fn run_enqueue(&self, f: impl FnOnce(Token)) {
critical_section::with(|cs| {
if self.update_with_cs(cs, |s| {
let ok = *s & STATE_RUN_QUEUED == 0;
*s |= STATE_RUN_QUEUED;
ok
}) {
f(cs);
}
});
}
/// Unmark the task as run-queued. Return whether the task is spawned.
#[inline(always)]
pub fn run_dequeue(&self, cs: CriticalSection<'_>) {
self.update_with_cs(cs, |s| *s &= !STATE_RUN_QUEUED)
}
}

View File

@ -0,0 +1,73 @@
//! Timer queue operations.
use core::cell::Cell;
use super::TaskRef;
#[cfg(feature = "_timer-item-payload")]
macro_rules! define_opaque {
($size:tt) => {
/// An opaque data type.
#[repr(align($size))]
pub struct OpaqueData {
data: [u8; $size],
}
impl OpaqueData {
const fn new() -> Self {
Self { data: [0; $size] }
}
/// Access the data as a reference to a type `T`.
///
/// Safety:
///
/// The caller must ensure that the size of the type `T` is less than, or equal to
/// the size of the payload, and must ensure that the alignment of the type `T` is
/// less than, or equal to the alignment of the payload.
///
/// The type must be valid when zero-initialized.
pub unsafe fn as_ref<T>(&self) -> &T {
&*(self.data.as_ptr() as *const T)
}
}
};
}
#[cfg(feature = "timer-item-payload-size-1")]
define_opaque!(1);
#[cfg(feature = "timer-item-payload-size-2")]
define_opaque!(2);
#[cfg(feature = "timer-item-payload-size-4")]
define_opaque!(4);
#[cfg(feature = "timer-item-payload-size-8")]
define_opaque!(8);
/// An item in the timer queue.
pub struct TimerQueueItem {
/// The next item in the queue.
///
/// If this field contains `Some`, the item is in the queue. The last item in the queue has a
/// value of `Some(dangling_pointer)`
pub next: Cell<Option<TaskRef>>,
/// The time at which this item expires.
pub expires_at: Cell<u64>,
/// Some implementation-defined, zero-initialized piece of data.
#[cfg(feature = "_timer-item-payload")]
pub payload: OpaqueData,
}
unsafe impl Sync for TimerQueueItem {}
impl TimerQueueItem {
pub(crate) const fn new() -> Self {
Self {
next: Cell::new(None),
expires_at: Cell::new(0),
#[cfg(feature = "_timer-item-payload")]
payload: OpaqueData::new(),
}
}
}

View File

@ -0,0 +1,84 @@
#![allow(unused)]
use crate::raw::{SyncExecutor, TaskRef};
#[cfg(not(feature = "rtos-trace"))]
extern "Rust" {
fn _embassy_trace_task_new(executor_id: u32, task_id: u32);
fn _embassy_trace_task_exec_begin(executor_id: u32, task_id: u32);
fn _embassy_trace_task_exec_end(excutor_id: u32, task_id: u32);
fn _embassy_trace_task_ready_begin(executor_id: u32, task_id: u32);
fn _embassy_trace_executor_idle(executor_id: u32);
}
#[inline]
pub(crate) fn task_new(executor: &SyncExecutor, task: &TaskRef) {
#[cfg(not(feature = "rtos-trace"))]
unsafe {
_embassy_trace_task_new(executor as *const _ as u32, task.as_ptr() as u32)
}
#[cfg(feature = "rtos-trace")]
rtos_trace::trace::task_new(task.as_ptr() as u32);
}
#[inline]
pub(crate) fn task_ready_begin(executor: &SyncExecutor, task: &TaskRef) {
#[cfg(not(feature = "rtos-trace"))]
unsafe {
_embassy_trace_task_ready_begin(executor as *const _ as u32, task.as_ptr() as u32)
}
#[cfg(feature = "rtos-trace")]
rtos_trace::trace::task_ready_begin(task.as_ptr() as u32);
}
#[inline]
pub(crate) fn task_exec_begin(executor: &SyncExecutor, task: &TaskRef) {
#[cfg(not(feature = "rtos-trace"))]
unsafe {
_embassy_trace_task_exec_begin(executor as *const _ as u32, task.as_ptr() as u32)
}
#[cfg(feature = "rtos-trace")]
rtos_trace::trace::task_exec_begin(task.as_ptr() as u32);
}
#[inline]
pub(crate) fn task_exec_end(executor: &SyncExecutor, task: &TaskRef) {
#[cfg(not(feature = "rtos-trace"))]
unsafe {
_embassy_trace_task_exec_end(executor as *const _ as u32, task.as_ptr() as u32)
}
#[cfg(feature = "rtos-trace")]
rtos_trace::trace::task_exec_end();
}
#[inline]
pub(crate) fn executor_idle(executor: &SyncExecutor) {
#[cfg(not(feature = "rtos-trace"))]
unsafe {
_embassy_trace_executor_idle(executor as *const _ as u32)
}
#[cfg(feature = "rtos-trace")]
rtos_trace::trace::system_idle();
}
#[cfg(feature = "rtos-trace")]
impl rtos_trace::RtosTraceOSCallbacks for crate::raw::SyncExecutor {
fn task_list() {
// We don't know what tasks exist, so we can't send them.
}
fn time() -> u64 {
const fn gcd(a: u64, b: u64) -> u64 {
if b == 0 {
a
} else {
gcd(b, a % b)
}
}
const GCD_1M: u64 = gcd(embassy_time_driver::TICK_HZ, 1_000_000);
embassy_time_driver::now() * (1_000_000 / GCD_1M) / (embassy_time_driver::TICK_HZ / GCD_1M)
}
}
#[cfg(feature = "rtos-trace")]
rtos_trace::global_os_callbacks! {SyncExecutor}

View File

@ -0,0 +1,57 @@
use core::cell::UnsafeCell;
use core::mem::MaybeUninit;
use core::ptr;
pub(crate) struct UninitCell<T>(MaybeUninit<UnsafeCell<T>>);
impl<T> UninitCell<T> {
pub const fn uninit() -> Self {
Self(MaybeUninit::uninit())
}
pub unsafe fn as_mut_ptr(&self) -> *mut T {
(*self.0.as_ptr()).get()
}
#[allow(clippy::mut_from_ref)]
pub unsafe fn as_mut(&self) -> &mut T {
&mut *self.as_mut_ptr()
}
#[inline(never)]
pub unsafe fn write_in_place(&self, func: impl FnOnce() -> T) {
ptr::write(self.as_mut_ptr(), func())
}
pub unsafe fn drop_in_place(&self) {
ptr::drop_in_place(self.as_mut_ptr())
}
}
unsafe impl<T> Sync for UninitCell<T> {}
#[repr(transparent)]
pub struct SyncUnsafeCell<T> {
value: UnsafeCell<T>,
}
unsafe impl<T: Sync> Sync for SyncUnsafeCell<T> {}
impl<T> SyncUnsafeCell<T> {
#[inline]
pub const fn new(value: T) -> Self {
Self {
value: UnsafeCell::new(value),
}
}
pub unsafe fn set(&self, value: T) {
*self.value.get() = value;
}
pub unsafe fn get(&self) -> T
where
T: Copy,
{
*self.value.get()
}
}

View File

@ -0,0 +1,42 @@
use core::task::{RawWaker, RawWakerVTable, Waker};
use super::{wake_task, TaskHeader, TaskRef};
static VTABLE: RawWakerVTable = RawWakerVTable::new(clone, wake, wake, drop);
unsafe fn clone(p: *const ()) -> RawWaker {
RawWaker::new(p, &VTABLE)
}
unsafe fn wake(p: *const ()) {
wake_task(TaskRef::from_ptr(p as *const TaskHeader))
}
unsafe fn drop(_: *const ()) {
// nop
}
pub(crate) unsafe fn from_task(p: TaskRef) -> Waker {
Waker::from_raw(RawWaker::new(p.as_ptr() as _, &VTABLE))
}
/// Get a task pointer from a waker.
///
/// This can be used as an optimization in wait queues to store task pointers
/// (1 word) instead of full Wakers (2 words). This saves a bit of RAM and helps
/// avoid dynamic dispatch.
///
/// You can use the returned task pointer to wake the task with [`wake_task`](super::wake_task).
///
/// # Panics
///
/// Panics if the waker is not created by the Embassy executor.
pub fn task_from_waker(waker: &Waker) -> TaskRef {
// make sure to compare vtable addresses. Doing `==` on the references
// will compare the contents, which is slower.
if waker.vtable() as *const _ != &VTABLE as *const _ {
panic!("Found waker not created by the Embassy executor. `embassy_time::Timer` only works with the Embassy executor.")
}
// safety: our wakers are always created with `TaskRef::as_ptr`
unsafe { TaskRef::from_ptr(waker.data() as *const TaskHeader) }
}

View File

@ -0,0 +1,34 @@
use core::ptr::NonNull;
use core::task::Waker;
use super::{wake_task, TaskHeader, TaskRef};
pub(crate) unsafe fn from_task(p: TaskRef) -> Waker {
Waker::from_turbo_ptr(NonNull::new_unchecked(p.as_ptr() as _))
}
/// Get a task pointer from a waker.
///
/// This can be used as an optimization in wait queues to store task pointers
/// (1 word) instead of full Wakers (2 words). This saves a bit of RAM and helps
/// avoid dynamic dispatch.
///
/// You can use the returned task pointer to wake the task with [`wake_task`](super::wake_task).
///
/// # Panics
///
/// Panics if the waker is not created by the Embassy executor.
pub fn task_from_waker(waker: &Waker) -> TaskRef {
let ptr = waker.as_turbo_ptr().as_ptr();
// safety: our wakers are always created with `TaskRef::as_ptr`
unsafe { TaskRef::from_ptr(ptr as *const TaskHeader) }
}
#[inline(never)]
#[no_mangle]
fn _turbo_wake(ptr: NonNull<()>) {
// safety: our wakers are always created with `TaskRef::as_ptr`
let task = unsafe { TaskRef::from_ptr(ptr.as_ptr() as *const TaskHeader) };
wake_task(task)
}

View File

@ -0,0 +1,232 @@
use core::future::{poll_fn, Future};
use core::marker::PhantomData;
use core::mem;
use core::sync::atomic::Ordering;
use core::task::Poll;
use super::raw;
/// Token to spawn a newly-created task in an executor.
///
/// When calling a task function (like `#[embassy_executor::task] async fn my_task() { ... }`), the returned
/// value is a `SpawnToken` that represents an instance of the task, ready to spawn. You must
/// then spawn it into an executor, typically with [`Spawner::spawn()`].
///
/// The generic parameter `S` determines whether the task can be spawned in executors
/// in other threads or not. If `S: Send`, it can, which allows spawning it into a [`SendSpawner`].
/// If not, it can't, so it can only be spawned into the current thread's executor, with [`Spawner`].
///
/// # Panics
///
/// Dropping a SpawnToken instance panics. You may not "abort" spawning a task in this way.
/// Once you've invoked a task function and obtained a SpawnToken, you *must* spawn it.
#[must_use = "Calling a task function does nothing on its own. You must spawn the returned SpawnToken, typically with Spawner::spawn()"]
pub struct SpawnToken<S> {
raw_task: Option<raw::TaskRef>,
phantom: PhantomData<*mut S>,
}
impl<S> SpawnToken<S> {
pub(crate) unsafe fn new(raw_task: raw::TaskRef) -> Self {
Self {
raw_task: Some(raw_task),
phantom: PhantomData,
}
}
/// Return a SpawnToken that represents a failed spawn.
pub fn new_failed() -> Self {
Self {
raw_task: None,
phantom: PhantomData,
}
}
}
impl<S> Drop for SpawnToken<S> {
fn drop(&mut self) {
// TODO deallocate the task instead.
panic!("SpawnToken instances may not be dropped. You must pass them to Spawner::spawn()")
}
}
/// Error returned when spawning a task.
#[derive(Copy, Clone)]
pub enum SpawnError {
/// Too many instances of this task are already running.
///
/// By default, a task marked with `#[embassy_executor::task]` can only have one instance
/// running at a time. You may allow multiple instances to run in parallel with
/// `#[embassy_executor::task(pool_size = 4)]`, at the cost of higher RAM usage.
Busy,
}
impl core::fmt::Debug for SpawnError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
core::fmt::Display::fmt(self, f)
}
}
impl core::fmt::Display for SpawnError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
SpawnError::Busy => write!(f, "Busy - Too many instances of this task are already running. Check the `pool_size` attribute of the task."),
}
}
}
#[cfg(feature = "defmt")]
impl defmt::Format for SpawnError {
fn format(&self, f: defmt::Formatter) {
match self {
SpawnError::Busy => defmt::write!(f, "Busy - Too many instances of this task are already running. Check the `pool_size` attribute of the task."),
}
}
}
impl core::error::Error for SpawnError {}
/// Handle to spawn tasks into an executor.
///
/// This Spawner can spawn any task (Send and non-Send ones), but it can
/// only be used in the executor thread (it is not Send itself).
///
/// If you want to spawn tasks from another thread, use [SendSpawner].
#[derive(Copy, Clone)]
pub struct Spawner {
executor: &'static raw::Executor,
not_send: PhantomData<*mut ()>,
}
impl Spawner {
pub(crate) fn new(executor: &'static raw::Executor) -> Self {
Self {
executor,
not_send: PhantomData,
}
}
/// Get a Spawner for the current executor.
///
/// This function is `async` just to get access to the current async
/// context. It returns instantly, it does not block/yield.
///
/// # Panics
///
/// Panics if the current executor is not an Embassy executor.
pub fn for_current_executor() -> impl Future<Output = Self> {
poll_fn(|cx| {
let task = raw::task_from_waker(cx.waker());
let executor = unsafe {
task.header()
.executor
.load(Ordering::Relaxed)
.as_ref()
.unwrap_unchecked()
};
let executor = unsafe { raw::Executor::wrap(executor) };
Poll::Ready(Self::new(executor))
})
}
/// Spawn a task into an executor.
///
/// You obtain the `token` by calling a task function (i.e. one marked with `#[embassy_executor::task]`).
pub fn spawn<S>(&self, token: SpawnToken<S>) -> Result<(), SpawnError> {
let task = token.raw_task;
mem::forget(token);
match task {
Some(task) => {
unsafe { self.executor.spawn(task) };
Ok(())
}
None => Err(SpawnError::Busy),
}
}
// Used by the `embassy_executor_macros::main!` macro to throw an error when spawn
// fails. This is here to allow conditional use of `defmt::unwrap!`
// without introducing a `defmt` feature in the `embassy_executor_macros` package,
// which would require use of `-Z namespaced-features`.
/// Spawn a task into an executor, panicking on failure.
///
/// # Panics
///
/// Panics if the spawning fails.
pub fn must_spawn<S>(&self, token: SpawnToken<S>) {
unwrap!(self.spawn(token));
}
/// Convert this Spawner to a SendSpawner. This allows you to send the
/// spawner to other threads, but the spawner loses the ability to spawn
/// non-Send tasks.
pub fn make_send(&self) -> SendSpawner {
SendSpawner::new(&self.executor.inner)
}
}
/// Handle to spawn tasks into an executor from any thread.
///
/// This Spawner can be used from any thread (it is Send), but it can
/// only spawn Send tasks. The reason for this is spawning is effectively
/// "sending" the tasks to the executor thread.
///
/// If you want to spawn non-Send tasks, use [Spawner].
#[derive(Copy, Clone)]
pub struct SendSpawner {
executor: &'static raw::SyncExecutor,
}
impl SendSpawner {
pub(crate) fn new(executor: &'static raw::SyncExecutor) -> Self {
Self { executor }
}
/// Get a Spawner for the current executor.
///
/// This function is `async` just to get access to the current async
/// context. It returns instantly, it does not block/yield.
///
/// # Panics
///
/// Panics if the current executor is not an Embassy executor.
pub fn for_current_executor() -> impl Future<Output = Self> {
poll_fn(|cx| {
let task = raw::task_from_waker(cx.waker());
let executor = unsafe {
task.header()
.executor
.load(Ordering::Relaxed)
.as_ref()
.unwrap_unchecked()
};
Poll::Ready(Self::new(executor))
})
}
/// Spawn a task into an executor.
///
/// You obtain the `token` by calling a task function (i.e. one marked with `#[embassy_executor::task]`).
pub fn spawn<S: Send>(&self, token: SpawnToken<S>) -> Result<(), SpawnError> {
let header = token.raw_task;
mem::forget(token);
match header {
Some(header) => {
unsafe { self.executor.spawn(header) };
Ok(())
}
None => Err(SpawnError::Busy),
}
}
/// Spawn a task into an executor, panicking on failure.
///
/// # Panics
///
/// Panics if the spawning fails.
pub fn must_spawn<S: Send>(&self, token: SpawnToken<S>) {
unwrap!(self.spawn(token));
}
}

View File

@ -0,0 +1,285 @@
#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))]
use std::boxed::Box;
use std::future::poll_fn;
use std::sync::{Arc, Mutex};
use std::task::Poll;
use embassy_executor::raw::Executor;
use embassy_executor::task;
#[export_name = "__pender"]
fn __pender(context: *mut ()) {
unsafe {
let trace = &*(context as *const Trace);
trace.push("pend");
}
}
#[derive(Clone)]
struct Trace {
trace: Arc<Mutex<Vec<&'static str>>>,
}
impl Trace {
fn new() -> Self {
Self {
trace: Arc::new(Mutex::new(Vec::new())),
}
}
fn push(&self, value: &'static str) {
self.trace.lock().unwrap().push(value)
}
fn get(&self) -> Vec<&'static str> {
self.trace.lock().unwrap().clone()
}
}
fn setup() -> (&'static Executor, Trace) {
let trace = Trace::new();
let context = Box::leak(Box::new(trace.clone())) as *mut _ as *mut ();
let executor = &*Box::leak(Box::new(Executor::new(context)));
(executor, trace)
}
#[test]
fn executor_noop() {
let (executor, trace) = setup();
unsafe { executor.poll() };
assert!(trace.get().is_empty())
}
#[test]
fn executor_task() {
#[task]
async fn task1(trace: Trace) {
trace.push("poll task1")
}
let (executor, trace) = setup();
executor.spawner().spawn(task1(trace.clone())).unwrap();
unsafe { executor.poll() };
unsafe { executor.poll() };
assert_eq!(
trace.get(),
&[
"pend", // spawning a task pends the executor
"poll task1", // poll only once.
]
)
}
#[test]
fn executor_task_self_wake() {
#[task]
async fn task1(trace: Trace) {
poll_fn(|cx| {
trace.push("poll task1");
cx.waker().wake_by_ref();
Poll::Pending
})
.await
}
let (executor, trace) = setup();
executor.spawner().spawn(task1(trace.clone())).unwrap();
unsafe { executor.poll() };
unsafe { executor.poll() };
assert_eq!(
trace.get(),
&[
"pend", // spawning a task pends the executor
"poll task1", //
"pend", // task self-wakes
"poll task1", //
"pend", // task self-wakes
]
)
}
#[test]
fn executor_task_self_wake_twice() {
#[task]
async fn task1(trace: Trace) {
poll_fn(|cx| {
trace.push("poll task1");
cx.waker().wake_by_ref();
trace.push("poll task1 wake 2");
cx.waker().wake_by_ref();
Poll::Pending
})
.await
}
let (executor, trace) = setup();
executor.spawner().spawn(task1(trace.clone())).unwrap();
unsafe { executor.poll() };
unsafe { executor.poll() };
assert_eq!(
trace.get(),
&[
"pend", // spawning a task pends the executor
"poll task1", //
"pend", // task self-wakes
"poll task1 wake 2", // task self-wakes again, shouldn't pend
"poll task1", //
"pend", // task self-wakes
"poll task1 wake 2", // task self-wakes again, shouldn't pend
]
)
}
#[test]
fn waking_after_completion_does_not_poll() {
use embassy_sync::waitqueue::AtomicWaker;
#[task]
async fn task1(trace: Trace, waker: &'static AtomicWaker) {
poll_fn(|cx| {
trace.push("poll task1");
waker.register(cx.waker());
Poll::Ready(())
})
.await
}
let waker = Box::leak(Box::new(AtomicWaker::new()));
let (executor, trace) = setup();
executor.spawner().spawn(task1(trace.clone(), waker)).unwrap();
unsafe { executor.poll() };
waker.wake();
unsafe { executor.poll() };
// Exited task may be waken but is not polled
waker.wake();
waker.wake();
unsafe { executor.poll() }; // Clears running status
// Can respawn waken-but-dead task
executor.spawner().spawn(task1(trace.clone(), waker)).unwrap();
unsafe { executor.poll() };
assert_eq!(
trace.get(),
&[
"pend", // spawning a task pends the executor
"poll task1", //
"pend", // manual wake, gets cleared by poll
"pend", // manual wake, single pend for two wakes
"pend", // respawning a task pends the executor
"poll task1", //
]
)
}
#[test]
fn waking_with_old_waker_after_respawn() {
use embassy_sync::waitqueue::AtomicWaker;
async fn yield_now(trace: Trace) {
let mut yielded = false;
poll_fn(|cx| {
if yielded {
Poll::Ready(())
} else {
trace.push("yield_now");
yielded = true;
cx.waker().wake_by_ref();
Poll::Pending
}
})
.await
}
#[task]
async fn task1(trace: Trace, waker: &'static AtomicWaker) {
yield_now(trace.clone()).await;
poll_fn(|cx| {
trace.push("poll task1");
waker.register(cx.waker());
Poll::Ready(())
})
.await;
}
let waker = Box::leak(Box::new(AtomicWaker::new()));
let (executor, trace) = setup();
executor.spawner().spawn(task1(trace.clone(), waker)).unwrap();
unsafe { executor.poll() };
unsafe { executor.poll() }; // progress to registering the waker
waker.wake();
unsafe { executor.poll() };
// Task has exited
assert_eq!(
trace.get(),
&[
"pend", // spawning a task pends the executor
"yield_now", //
"pend", // yield_now wakes the task
"poll task1", //
"pend", // task self-wakes
]
);
// Can respawn task on another executor
let (other_executor, other_trace) = setup();
other_executor
.spawner()
.spawn(task1(other_trace.clone(), waker))
.unwrap();
unsafe { other_executor.poll() }; // just run to the yield_now
waker.wake(); // trigger old waker registration
unsafe { executor.poll() };
unsafe { other_executor.poll() };
// First executor's trace has not changed
assert_eq!(
trace.get(),
&[
"pend", // spawning a task pends the executor
"yield_now", //
"pend", // yield_now wakes the task
"poll task1", //
"pend", // task self-wakes
]
);
assert_eq!(
other_trace.get(),
&[
"pend", // spawning a task pends the executor
"yield_now", //
"pend", // manual wake, gets cleared by poll
"poll task1", //
]
);
}
#[test]
fn executor_task_cfg_args() {
// simulate cfg'ing away argument c
#[task]
async fn task1(a: u32, b: u32, #[cfg(any())] c: u32) {
let (_, _) = (a, b);
}
#[task]
async fn task2(a: u32, b: u32, #[cfg(all())] c: u32) {
let (_, _, _) = (a, b, c);
}
}

View File

@ -0,0 +1,23 @@
#[cfg(not(miri))]
#[test]
fn ui() {
let t = trybuild::TestCases::new();
t.compile_fail("tests/ui/abi.rs");
t.compile_fail("tests/ui/bad_return.rs");
t.compile_fail("tests/ui/generics.rs");
t.compile_fail("tests/ui/impl_trait_nested.rs");
t.compile_fail("tests/ui/impl_trait.rs");
t.compile_fail("tests/ui/impl_trait_static.rs");
t.compile_fail("tests/ui/nonstatic_ref_anon_nested.rs");
t.compile_fail("tests/ui/nonstatic_ref_anon.rs");
t.compile_fail("tests/ui/nonstatic_ref_elided.rs");
t.compile_fail("tests/ui/nonstatic_ref_generic.rs");
t.compile_fail("tests/ui/nonstatic_struct_anon.rs");
#[cfg(not(feature = "nightly"))] // we can't catch this case with the macro, so the output changes on nightly.
t.compile_fail("tests/ui/nonstatic_struct_elided.rs");
t.compile_fail("tests/ui/nonstatic_struct_generic.rs");
t.compile_fail("tests/ui/not_async.rs");
t.compile_fail("tests/ui/self_ref.rs");
t.compile_fail("tests/ui/self.rs");
t.compile_fail("tests/ui/where_clause.rs");
}

View File

@ -0,0 +1,8 @@
#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))]
struct Foo<'a>(&'a ());
#[embassy_executor::task]
async extern "C" fn task() {}
fn main() {}

View File

@ -0,0 +1,5 @@
error: task functions must not have an ABI qualifier
--> tests/ui/abi.rs:6:1
|
6 | async extern "C" fn task() {}
| ^^^^^^^^^^^^^^^^^^^^^^^^^^

View File

@ -0,0 +1,10 @@
#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))]
struct Foo<'a>(&'a ());
#[embassy_executor::task]
async fn task() -> u32 {
5
}
fn main() {}

View File

@ -0,0 +1,5 @@
error: task functions must either not return a value, return `()` or return `!`
--> tests/ui/bad_return.rs:6:1
|
6 | async fn task() -> u32 {
| ^^^^^^^^^^^^^^^^^^^^^^

View File

@ -0,0 +1,8 @@
#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))]
struct Foo<'a>(&'a ());
#[embassy_executor::task]
async fn task<T: Sized>(_t: T) {}
fn main() {}

View File

@ -0,0 +1,5 @@
error: task functions must not be generic
--> tests/ui/generics.rs:6:1
|
6 | async fn task<T: Sized>(_t: T) {}
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

View File

@ -0,0 +1,6 @@
#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))]
#[embassy_executor::task]
async fn foo(_x: impl Sized) {}
fn main() {}

View File

@ -0,0 +1,5 @@
error: `impl Trait` is not allowed in task arguments. It is syntax sugar for generics, and tasks can't be generic.
--> tests/ui/impl_trait.rs:4:18
|
4 | async fn foo(_x: impl Sized) {}
| ^^^^^^^^^^

View File

@ -0,0 +1,8 @@
#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))]
struct Foo<T>(T);
#[embassy_executor::task]
async fn foo(_x: Foo<impl Sized + 'static>) {}
fn main() {}

View File

@ -0,0 +1,5 @@
error: `impl Trait` is not allowed in task arguments. It is syntax sugar for generics, and tasks can't be generic.
--> tests/ui/impl_trait_nested.rs:6:22
|
6 | async fn foo(_x: Foo<impl Sized + 'static>) {}
| ^^^^^^^^^^^^^^^^^^^^

View File

@ -0,0 +1,6 @@
#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))]
#[embassy_executor::task]
async fn foo(_x: impl Sized + 'static) {}
fn main() {}

View File

@ -0,0 +1,5 @@
error: `impl Trait` is not allowed in task arguments. It is syntax sugar for generics, and tasks can't be generic.
--> tests/ui/impl_trait_static.rs:4:18
|
4 | async fn foo(_x: impl Sized + 'static) {}
| ^^^^^^^^^^^^^^^^^^^^

View File

@ -0,0 +1,6 @@
#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))]
#[embassy_executor::task]
async fn foo(_x: &'_ u32) {}
fn main() {}

View File

@ -0,0 +1,5 @@
error: Arguments for tasks must live forever. Try using the `'static` lifetime.
--> tests/ui/nonstatic_ref_anon.rs:4:19
|
4 | async fn foo(_x: &'_ u32) {}
| ^^

View File

@ -0,0 +1,6 @@
#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))]
#[embassy_executor::task]
async fn foo(_x: &'static &'_ u32) {}
fn main() {}

View File

@ -0,0 +1,5 @@
error: Arguments for tasks must live forever. Try using the `'static` lifetime.
--> tests/ui/nonstatic_ref_anon_nested.rs:4:28
|
4 | async fn foo(_x: &'static &'_ u32) {}
| ^^

View File

@ -0,0 +1,6 @@
#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))]
#[embassy_executor::task]
async fn foo(_x: &u32) {}
fn main() {}

View File

@ -0,0 +1,5 @@
error: Arguments for tasks must live forever. Try using the `'static` lifetime.
--> tests/ui/nonstatic_ref_elided.rs:4:18
|
4 | async fn foo(_x: &u32) {}
| ^

View File

@ -0,0 +1,6 @@
#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))]
#[embassy_executor::task]
async fn foo<'a>(_x: &'a u32) {}
fn main() {}

View File

@ -0,0 +1,11 @@
error: task functions must not be generic
--> tests/ui/nonstatic_ref_generic.rs:4:1
|
4 | async fn foo<'a>(_x: &'a u32) {}
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
error: Arguments for tasks must live forever. Try using the `'static` lifetime.
--> tests/ui/nonstatic_ref_generic.rs:4:23
|
4 | async fn foo<'a>(_x: &'a u32) {}
| ^^

Some files were not shown because too many files have changed in this diff Show More