init project
This commit is contained in:
commit
788d9bd6ea
BIN
cyw43-firmware/43439A0.bin
Normal file
BIN
cyw43-firmware/43439A0.bin
Normal file
Binary file not shown.
BIN
cyw43-firmware/43439A0_btfw.bin
Normal file
BIN
cyw43-firmware/43439A0_btfw.bin
Normal file
Binary file not shown.
BIN
cyw43-firmware/43439A0_clm.bin
Normal file
BIN
cyw43-firmware/43439A0_clm.bin
Normal file
Binary file not shown.
49
cyw43-firmware/LICENSE-permissive-binary-license-1.0.txt
Normal file
49
cyw43-firmware/LICENSE-permissive-binary-license-1.0.txt
Normal 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
14
cyw43-firmware/README.md
Normal 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
25
cyw43-pio/CHANGELOG.md
Normal 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
22
cyw43-pio/Cargo.toml
Normal 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
3
cyw43-pio/README.md
Normal 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
245
cyw43-pio/src/lib.rs
Normal 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
30
cyw43/CHANGELOG.md
Normal 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
48
cyw43/Cargo.toml
Normal 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
55
cyw43/README.md
Normal 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
508
cyw43/src/bluetooth.rs
Normal 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
388
cyw43/src/bus.rs
Normal 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
670
cyw43/src/consts.rs
Normal 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
745
cyw43/src/control.rs
Normal 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
481
cyw43/src/countries.rs
Normal 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
400
cyw43/src/events.rs
Normal 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
270
cyw43/src/fmt.rs
Normal 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
123
cyw43/src/ioctl.rs
Normal 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
299
cyw43/src/lib.rs
Normal 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
48
cyw43/src/nvram.rs
Normal 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
666
cyw43/src/runner.rs
Normal 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
555
cyw43/src/structs.rs
Normal 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
20
cyw43/src/util.rs
Normal 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
|
||||
}
|
||||
26
embassy-embedded-hal/CHANGELOG.md
Normal file
26
embassy-embedded-hal/CHANGELOG.md
Normal 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
|
||||
42
embassy-embedded-hal/Cargo.toml
Normal file
42
embassy-embedded-hal/Cargo.toml
Normal 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"
|
||||
12
embassy-embedded-hal/README.md
Normal file
12
embassy-embedded-hal/README.md
Normal 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.
|
||||
149
embassy-embedded-hal/src/adapter/blocking_async.rs
Normal file
149
embassy-embedded-hal/src/adapter/blocking_async.rs
Normal 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 {}
|
||||
7
embassy-embedded-hal/src/adapter/mod.rs
Normal file
7
embassy-embedded-hal/src/adapter/mod.rs
Normal 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;
|
||||
169
embassy-embedded-hal/src/adapter/yielding_async.rs
Normal file
169
embassy-embedded-hal/src/adapter/yielding_async.rs
Normal 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]);
|
||||
}
|
||||
}
|
||||
225
embassy-embedded-hal/src/flash/concat_flash.rs
Normal file
225
embassy-embedded-hal/src/flash/concat_flash.rs
Normal 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]);
|
||||
}
|
||||
}
|
||||
125
embassy-embedded-hal/src/flash/mem_flash.rs
Normal file
125
embassy-embedded-hal/src/flash/mem_flash.rs
Normal 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(())
|
||||
}
|
||||
}
|
||||
8
embassy-embedded-hal/src/flash/mod.rs
Normal file
8
embassy-embedded-hal/src/flash/mod.rs
Normal 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;
|
||||
149
embassy-embedded-hal/src/flash/partition/asynch.rs
Normal file
149
embassy-embedded-hal/src/flash/partition/asynch.rs
Normal 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());
|
||||
}
|
||||
}
|
||||
159
embassy-embedded-hal/src/flash/partition/blocking.rs
Normal file
159
embassy-embedded-hal/src/flash/partition/blocking.rs
Normal 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());
|
||||
}
|
||||
}
|
||||
28
embassy-embedded-hal/src/flash/partition/mod.rs
Normal file
28
embassy-embedded-hal/src/flash/partition/mod.rs
Normal 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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
39
embassy-embedded-hal/src/lib.rs
Normal file
39
embassy-embedded-hal/src/lib.rs
Normal 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;
|
||||
}
|
||||
166
embassy-embedded-hal/src/shared_bus/asynch/i2c.rs
Normal file
166
embassy-embedded-hal/src/shared_bus/asynch/i2c.rs
Normal 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(())
|
||||
}
|
||||
}
|
||||
3
embassy-embedded-hal/src/shared_bus/asynch/mod.rs
Normal file
3
embassy-embedded-hal/src/shared_bus/asynch/mod.rs
Normal file
@ -0,0 +1,3 @@
|
||||
//! Asynchronous shared bus implementations for embedded-hal-async
|
||||
pub mod i2c;
|
||||
pub mod spi;
|
||||
212
embassy-embedded-hal/src/shared_bus/asynch/spi.rs
Normal file
212
embassy-embedded-hal/src/shared_bus/asynch/spi.rs
Normal 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)
|
||||
}
|
||||
}
|
||||
187
embassy-embedded-hal/src/shared_bus/blocking/i2c.rs
Normal file
187
embassy-embedded-hal/src/shared_bus/blocking/i2c.rs
Normal 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)
|
||||
})
|
||||
}
|
||||
}
|
||||
3
embassy-embedded-hal/src/shared_bus/blocking/mod.rs
Normal file
3
embassy-embedded-hal/src/shared_bus/blocking/mod.rs
Normal file
@ -0,0 +1,3 @@
|
||||
//! Blocking shared bus implementations for embedded-hal
|
||||
pub mod i2c;
|
||||
pub mod spi;
|
||||
167
embassy-embedded-hal/src/shared_bus/blocking/spi.rs
Normal file
167
embassy-embedded-hal/src/shared_bus/blocking/spi.rs
Normal 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)
|
||||
})
|
||||
}
|
||||
}
|
||||
59
embassy-embedded-hal/src/shared_bus/mod.rs
Normal file
59
embassy-embedded-hal/src/shared_bus/mod.rs
Normal 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,
|
||||
}
|
||||
}
|
||||
}
|
||||
25
embassy-executor-macros/Cargo.toml
Normal file
25
embassy-executor-macros/Cargo.toml
Normal 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 = []
|
||||
5
embassy-executor-macros/README.md
Normal file
5
embassy-executor-macros/README.md
Normal 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`.
|
||||
175
embassy-executor-macros/src/lib.rs
Normal file
175
embassy-executor-macros/src/lib.rs
Normal 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()
|
||||
}
|
||||
184
embassy-executor-macros/src/macros/main.rs
Normal file
184
embassy-executor-macros/src/macros/main.rs
Normal 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
|
||||
}
|
||||
2
embassy-executor-macros/src/macros/mod.rs
Normal file
2
embassy-executor-macros/src/macros/mod.rs
Normal file
@ -0,0 +1,2 @@
|
||||
pub mod main;
|
||||
pub mod task;
|
||||
220
embassy-executor-macros/src/macros/task.rs
Normal file
220
embassy-executor-macros/src/macros/task.rs
Normal 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);
|
||||
}
|
||||
74
embassy-executor-macros/src/util.rs
Normal file
74
embassy-executor-macros/src/util.rs
Normal 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());
|
||||
});
|
||||
}
|
||||
}
|
||||
97
embassy-executor/CHANGELOG.md
Normal file
97
embassy-executor/CHANGELOG.md
Normal 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
205
embassy-executor/Cargo.toml
Normal 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 you’re 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>
|
||||
37
embassy-executor/README.md
Normal file
37
embassy-executor/README.md
Normal 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
99
embassy-executor/build.rs
Normal 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);
|
||||
}
|
||||
126
embassy-executor/build_common.rs
Normal file
126
embassy-executor/build_common.rs
Normal 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))
|
||||
}
|
||||
}
|
||||
88
embassy-executor/gen_config.py
Normal file
88
embassy-executor/gen_config.py
Normal 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)
|
||||
72
embassy-executor/src/arch/avr.rs
Normal file
72
embassy-executor/src/arch/avr.rs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
231
embassy-executor/src/arch/cortex_m.rs
Normal file
231
embassy-executor/src/arch/cortex_m.rs
Normal 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()
|
||||
}
|
||||
}
|
||||
}
|
||||
80
embassy-executor/src/arch/riscv32.rs
Normal file
80
embassy-executor/src/arch/riscv32.rs
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
58
embassy-executor/src/arch/spin.rs
Normal file
58
embassy-executor/src/arch/spin.rs
Normal 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() };
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
94
embassy-executor/src/arch/std.rs
Normal file
94
embassy-executor/src/arch/std.rs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
83
embassy-executor/src/arch/wasm.rs
Normal file
83
embassy-executor/src/arch/wasm.rs
Normal 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
270
embassy-executor/src/fmt.rs
Normal 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
150
embassy-executor/src/lib.rs
Normal 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 _) }
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
571
embassy-executor/src/raw/mod.rs
Normal file
571
embassy-executor/src/raw/mod.rs
Normal 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);
|
||||
}
|
||||
});
|
||||
}
|
||||
88
embassy-executor/src/raw/run_queue_atomics.rs
Normal file
88
embassy-executor/src/raw/run_queue_atomics.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
74
embassy-executor/src/raw/run_queue_critical_section.rs
Normal file
74
embassy-executor/src/raw/run_queue_critical_section.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
58
embassy-executor/src/raw/state_atomics.rs
Normal file
58
embassy-executor/src/raw/state_atomics.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
83
embassy-executor/src/raw/state_atomics_arm.rs
Normal file
83
embassy-executor/src/raw/state_atomics_arm.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
73
embassy-executor/src/raw/state_critical_section.rs
Normal file
73
embassy-executor/src/raw/state_critical_section.rs
Normal 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)
|
||||
}
|
||||
}
|
||||
73
embassy-executor/src/raw/timer_queue.rs
Normal file
73
embassy-executor/src/raw/timer_queue.rs
Normal 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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
84
embassy-executor/src/raw/trace.rs
Normal file
84
embassy-executor/src/raw/trace.rs
Normal 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}
|
||||
57
embassy-executor/src/raw/util.rs
Normal file
57
embassy-executor/src/raw/util.rs
Normal 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()
|
||||
}
|
||||
}
|
||||
42
embassy-executor/src/raw/waker.rs
Normal file
42
embassy-executor/src/raw/waker.rs
Normal 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) }
|
||||
}
|
||||
34
embassy-executor/src/raw/waker_turbo.rs
Normal file
34
embassy-executor/src/raw/waker_turbo.rs
Normal 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)
|
||||
}
|
||||
232
embassy-executor/src/spawner.rs
Normal file
232
embassy-executor/src/spawner.rs
Normal 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));
|
||||
}
|
||||
}
|
||||
285
embassy-executor/tests/test.rs
Normal file
285
embassy-executor/tests/test.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
23
embassy-executor/tests/ui.rs
Normal file
23
embassy-executor/tests/ui.rs
Normal 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");
|
||||
}
|
||||
8
embassy-executor/tests/ui/abi.rs
Normal file
8
embassy-executor/tests/ui/abi.rs
Normal 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() {}
|
||||
5
embassy-executor/tests/ui/abi.stderr
Normal file
5
embassy-executor/tests/ui/abi.stderr
Normal 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() {}
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
10
embassy-executor/tests/ui/bad_return.rs
Normal file
10
embassy-executor/tests/ui/bad_return.rs
Normal 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() {}
|
||||
5
embassy-executor/tests/ui/bad_return.stderr
Normal file
5
embassy-executor/tests/ui/bad_return.stderr
Normal 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 {
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^
|
||||
8
embassy-executor/tests/ui/generics.rs
Normal file
8
embassy-executor/tests/ui/generics.rs
Normal 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() {}
|
||||
5
embassy-executor/tests/ui/generics.stderr
Normal file
5
embassy-executor/tests/ui/generics.stderr
Normal 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) {}
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
6
embassy-executor/tests/ui/impl_trait.rs
Normal file
6
embassy-executor/tests/ui/impl_trait.rs
Normal 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() {}
|
||||
5
embassy-executor/tests/ui/impl_trait.stderr
Normal file
5
embassy-executor/tests/ui/impl_trait.stderr
Normal 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) {}
|
||||
| ^^^^^^^^^^
|
||||
8
embassy-executor/tests/ui/impl_trait_nested.rs
Normal file
8
embassy-executor/tests/ui/impl_trait_nested.rs
Normal 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() {}
|
||||
5
embassy-executor/tests/ui/impl_trait_nested.stderr
Normal file
5
embassy-executor/tests/ui/impl_trait_nested.stderr
Normal 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>) {}
|
||||
| ^^^^^^^^^^^^^^^^^^^^
|
||||
6
embassy-executor/tests/ui/impl_trait_static.rs
Normal file
6
embassy-executor/tests/ui/impl_trait_static.rs
Normal 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() {}
|
||||
5
embassy-executor/tests/ui/impl_trait_static.stderr
Normal file
5
embassy-executor/tests/ui/impl_trait_static.stderr
Normal 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) {}
|
||||
| ^^^^^^^^^^^^^^^^^^^^
|
||||
6
embassy-executor/tests/ui/nonstatic_ref_anon.rs
Normal file
6
embassy-executor/tests/ui/nonstatic_ref_anon.rs
Normal 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() {}
|
||||
5
embassy-executor/tests/ui/nonstatic_ref_anon.stderr
Normal file
5
embassy-executor/tests/ui/nonstatic_ref_anon.stderr
Normal 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) {}
|
||||
| ^^
|
||||
6
embassy-executor/tests/ui/nonstatic_ref_anon_nested.rs
Normal file
6
embassy-executor/tests/ui/nonstatic_ref_anon_nested.rs
Normal 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() {}
|
||||
@ -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) {}
|
||||
| ^^
|
||||
6
embassy-executor/tests/ui/nonstatic_ref_elided.rs
Normal file
6
embassy-executor/tests/ui/nonstatic_ref_elided.rs
Normal 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() {}
|
||||
5
embassy-executor/tests/ui/nonstatic_ref_elided.stderr
Normal file
5
embassy-executor/tests/ui/nonstatic_ref_elided.stderr
Normal 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) {}
|
||||
| ^
|
||||
6
embassy-executor/tests/ui/nonstatic_ref_generic.rs
Normal file
6
embassy-executor/tests/ui/nonstatic_ref_generic.rs
Normal 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() {}
|
||||
11
embassy-executor/tests/ui/nonstatic_ref_generic.stderr
Normal file
11
embassy-executor/tests/ui/nonstatic_ref_generic.stderr
Normal 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
Loading…
x
Reference in New Issue
Block a user