Merge branch 'embassy-rs:main' into main

This commit is contained in:
Mateusz 2024-09-25 09:54:02 +02:00 committed by GitHub
commit 0f9cc53d92
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
146 changed files with 7353 additions and 1166 deletions

4
.github/ci/test.sh vendored
View File

@ -8,6 +8,10 @@ export RUSTUP_HOME=/ci/cache/rustup
export CARGO_HOME=/ci/cache/cargo
export CARGO_TARGET_DIR=/ci/cache/target
# needed for "dumb HTTP" transport support
# used when pointing stm32-metapac to a CI-built one.
export CARGO_NET_GIT_FETCH_WITH_CLI=true
cargo test --manifest-path ./embassy-futures/Cargo.toml
cargo test --manifest-path ./embassy-sync/Cargo.toml
cargo test --manifest-path ./embassy-embedded-hal/Cargo.toml

1
.gitignore vendored
View File

@ -5,3 +5,4 @@ Cargo.lock
third_party
/Cargo.toml
out/
.zed

6
ci.sh
View File

@ -47,7 +47,7 @@ cargo batch \
--- build --release --manifest-path embassy-sync/Cargo.toml --target thumbv6m-none-eabi --features defmt \
--- build --release --manifest-path embassy-time/Cargo.toml --target thumbv6m-none-eabi --features defmt,defmt-timestamp-uptime,generic-queue-8,mock-driver \
--- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,proto-ipv4,medium-ethernet,packet-trace \
--- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,proto-ipv4,igmp,medium-ethernet \
--- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,proto-ipv4,multicast,medium-ethernet \
--- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,dhcpv4,medium-ethernet \
--- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,dhcpv4,medium-ethernet,dhcpv4-hostname \
--- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,proto-ipv6,medium-ethernet \
@ -290,8 +290,9 @@ cargo batch \
$BUILD_EXTRA
# temporarily disabled, bluepill board got bricked
# temporarily disabled, these boards are dead.
rm -rf out/tests/stm32f103c8
rm -rf out/tests/nrf52840-dk
rm out/tests/stm32wb55rg/wpan_mac
rm out/tests/stm32wb55rg/wpan_ble
@ -305,6 +306,7 @@ rm out/tests/stm32u5a5zj/usart
# flaky, probably due to bad ringbuffered dma code.
rm out/tests/stm32l152re/usart_rx_ringbuffered
rm out/tests/stm32f207zg/usart_rx_ringbuffered
rm out/tests/stm32wl55jc/usart_rx_ringbuffered
if [[ -z "${TELEPROBE_TOKEN-}" ]]; then
echo No teleprobe token found, skipping running HIL tests

View File

@ -36,7 +36,7 @@ heapless = "0.8.0"
# Bluetooth deps
embedded-io-async = { version = "0.6.0", optional = true }
bt-hci = { git = "https://github.com/alexmoon/bt-hci.git", rev = "b9cd5954f6bd89b535cad9c418e9fdf12812d7c3", optional = true, default-features = false }
bt-hci = { version = "0.1.0", optional = true }
[package.metadata.embassy_docs]
src_base = "https://github.com/embassy-rs/embassy/blob/cyw43-v$VERSION/cyw43/src/"

View File

@ -113,17 +113,6 @@ pub(crate) const IRQ_F1_INTR: u16 = 0x2000;
pub(crate) const IRQ_F2_INTR: u16 = 0x4000;
pub(crate) const IRQ_F3_INTR: u16 = 0x8000;
pub(crate) const IOCTL_CMD_UP: u32 = 2;
pub(crate) const IOCTL_CMD_DOWN: u32 = 3;
pub(crate) const IOCTL_CMD_SET_SSID: u32 = 26;
pub(crate) const IOCTL_CMD_SET_CHANNEL: u32 = 30;
pub(crate) const IOCTL_CMD_DISASSOC: u32 = 52;
pub(crate) const IOCTL_CMD_ANTDIV: u32 = 64;
pub(crate) const IOCTL_CMD_SET_AP: u32 = 118;
pub(crate) const IOCTL_CMD_SET_VAR: u32 = 263;
pub(crate) const IOCTL_CMD_GET_VAR: u32 = 262;
pub(crate) const IOCTL_CMD_SET_PASSPHRASE: u32 = 268;
pub(crate) const CHANNEL_TYPE_CONTROL: u8 = 0;
pub(crate) const CHANNEL_TYPE_EVENT: u8 = 1;
pub(crate) const CHANNEL_TYPE_DATA: u8 = 2;
@ -376,3 +365,306 @@ impl core::fmt::Display for FormatInterrupt {
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;

View File

@ -35,7 +35,7 @@ pub struct Control<'a> {
ioctl_state: &'a IoctlState,
}
#[derive(Copy, Clone)]
#[derive(Copy, Clone, Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum ScanType {
Active,
@ -43,8 +43,9 @@ pub enum ScanType {
}
/// Scan options.
#[derive(Clone)]
#[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>>,
@ -74,6 +75,79 @@ impl Default for ScanOptions {
}
}
/// 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 {
@ -109,7 +183,7 @@ impl<'a> Control<'a> {
buf[0..8].copy_from_slice(b"clmload\x00");
buf[8..20].copy_from_slice(&header.to_bytes());
buf[20..][..chunk.len()].copy_from_slice(&chunk);
self.ioctl(IoctlType::Set, IOCTL_CMD_SET_VAR, 0, &mut buf[..8 + 12 + chunk.len()])
self.ioctl(IoctlType::Set, Ioctl::SetVar, 0, &mut buf[..8 + 12 + chunk.len()])
.await;
}
@ -145,7 +219,7 @@ impl<'a> Control<'a> {
Timer::after_millis(100).await;
// Set antenna to chip antenna
self.ioctl_set_u32(IOCTL_CMD_ANTDIV, 0, 0).await;
self.ioctl_set_u32(Ioctl::SetAntdiv, 0, 0).await;
self.set_iovar_u32("bus:txglom", 0).await;
Timer::after_millis(100).await;
@ -183,8 +257,8 @@ impl<'a> Control<'a> {
Timer::after_millis(100).await;
self.ioctl_set_u32(110, 0, 1).await; // SET_GMODE = auto
self.ioctl_set_u32(142, 0, 0).await; // SET_BAND = any
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;
@ -195,12 +269,12 @@ impl<'a> Control<'a> {
/// Set the WiFi interface up.
async fn up(&mut self) {
self.ioctl(IoctlType::Set, IOCTL_CMD_UP, 0, &mut []).await;
self.ioctl(IoctlType::Set, Ioctl::Up, 0, &mut []).await;
}
/// Set the interface down.
async fn down(&mut self) {
self.ioctl(IoctlType::Set, IOCTL_CMD_DOWN, 0, &mut []).await;
self.ioctl(IoctlType::Set, Ioctl::Down, 0, &mut []).await;
}
/// Set power management mode.
@ -213,17 +287,74 @@ impl<'a> Control<'a> {
self.set_iovar_u32("bcn_li_dtim", mode.dtim_period() as u32).await;
self.set_iovar_u32("assoc_listen", mode.assoc() as u32).await;
}
self.ioctl_set_u32(86, 0, mode_num).await;
self.ioctl_set_u32(Ioctl::SetPm, 0, mode_num).await;
}
/// Join an unprotected network with the provided ssid.
pub async fn join_open(&mut self, ssid: &str) -> Result<(), Error> {
pub async fn join(&mut self, ssid: &str, options: JoinOptions<'_>) -> Result<(), Error> {
self.set_iovar_u32("ampdu_ba_wsize", 8).await;
self.ioctl_set_u32(134, 0, 0).await; // wsec = open
self.set_iovar_u32x2("bsscfg:sup_wpa", 0, 0).await;
self.ioctl_set_u32(20, 0, 1).await; // set_infra = 1
self.ioctl_set_u32(22, 0, 0).await; // set_auth = open (0)
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 _,
@ -234,69 +365,13 @@ impl<'a> Control<'a> {
self.wait_for_join(i).await
}
/// Join a protected network with the provided ssid and [`PassphraseInfo`].
async fn join_wpa2_passphrase_info(&mut self, ssid: &str, passphrase_info: &PassphraseInfo) -> Result<(), Error> {
self.set_iovar_u32("ampdu_ba_wsize", 8).await;
self.ioctl_set_u32(134, 0, 4).await; // wsec = wpa2
self.set_iovar_u32x2("bsscfg:sup_wpa", 0, 1).await;
self.set_iovar_u32x2("bsscfg:sup_wpa2_eapver", 0, 0xFFFF_FFFF).await;
self.set_iovar_u32x2("bsscfg:sup_wpa_tmo", 0, 2500).await;
Timer::after_millis(100).await;
self.ioctl(
IoctlType::Set,
IOCTL_CMD_SET_PASSPHRASE,
0,
&mut passphrase_info.to_bytes(),
)
.await; // WLC_SET_WSEC_PMK
self.ioctl_set_u32(20, 0, 1).await; // set_infra = 1
self.ioctl_set_u32(22, 0, 0).await; // set_auth = 0 (open)
self.ioctl_set_u32(165, 0, 0x80).await; // set_wpa_auth
let mut i = SsidInfo {
len: ssid.len() as _,
ssid: [0; 32],
};
i.ssid[..ssid.len()].copy_from_slice(ssid.as_bytes());
self.wait_for_join(i).await
}
/// Join a protected network with the provided ssid and passphrase.
pub async fn join_wpa2(&mut self, ssid: &str, passphrase: &str) -> Result<(), Error> {
let mut pfi = PassphraseInfo {
len: passphrase.len() as _,
flags: 1,
passphrase: [0; 64],
};
pfi.passphrase[..passphrase.len()].copy_from_slice(passphrase.as_bytes());
self.join_wpa2_passphrase_info(ssid, &pfi).await
}
/// Join a protected network with the provided ssid and precomputed PSK.
pub async fn join_wpa2_psk(&mut self, ssid: &str, psk: &[u8; 32]) -> Result<(), Error> {
let mut pfi = PassphraseInfo {
len: psk.len() as _,
flags: 0,
passphrase: [0; 64],
};
pfi.passphrase[..psk.len()].copy_from_slice(psk);
self.join_wpa2_passphrase_info(ssid, &pfi).await
}
async fn wait_for_join(&mut self, i: SsidInfo) -> Result<(), Error> {
self.events.mask.enable(&[Event::SET_SSID, Event::AUTH]);
let mut subscriber = self.events.queue.subscriber().unwrap();
// the actual join operation starts here
// we make sure to enable events before so we don't miss any
// set_ssid
self.ioctl(IoctlType::Set, IOCTL_CMD_SET_SSID, 0, &mut i.to_bytes())
.await;
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
@ -357,7 +432,7 @@ impl<'a> Control<'a> {
self.up().await;
// Turn on AP mode
self.ioctl_set_u32(IOCTL_CMD_SET_AP, 0, 1).await;
self.ioctl_set_u32(Ioctl::SetAp, 0, 1).await;
// Set SSID
let mut i = SsidInfoWithIndex {
@ -371,7 +446,7 @@ impl<'a> Control<'a> {
self.set_iovar("bsscfg:ssid", &i.to_bytes()).await;
// Set channel number
self.ioctl_set_u32(IOCTL_CMD_SET_CHANNEL, 0, channel as u32).await;
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;
@ -388,7 +463,7 @@ impl<'a> Control<'a> {
passphrase: [0; 64],
};
pfi.passphrase[..passphrase.as_bytes().len()].copy_from_slice(passphrase.as_bytes());
self.ioctl(IoctlType::Set, IOCTL_CMD_SET_PASSPHRASE, 0, &mut pfi.to_bytes())
self.ioctl(IoctlType::Set, Ioctl::SetWsecPmk, 0, &mut pfi.to_bytes())
.await;
}
@ -405,7 +480,7 @@ impl<'a> Control<'a> {
self.set_iovar_u32x2("bss", 0, 0).await; // bss = BSS_DOWN
// Turn off AP mode
self.ioctl_set_u32(IOCTL_CMD_SET_AP, 0, 0).await;
self.ioctl_set_u32(Ioctl::SetAp, 0, 0).await;
// Temporarily set wifi down
self.down().await;
@ -484,11 +559,11 @@ impl<'a> Control<'a> {
}
async fn set_iovar(&mut self, name: &str, val: &[u8]) {
self.set_iovar_v::<64>(name, val).await
self.set_iovar_v::<196>(name, val).await
}
async fn set_iovar_v<const BUFSIZE: usize>(&mut self, name: &str, val: &[u8]) {
debug!("set {} = {:02x}", name, Bytes(val));
debug!("iovar set {} = {:02x}", name, Bytes(val));
let mut buf = [0; BUFSIZE];
buf[..name.len()].copy_from_slice(name.as_bytes());
@ -496,13 +571,13 @@ impl<'a> Control<'a> {
buf[name.len() + 1..][..val.len()].copy_from_slice(val);
let total_len = name.len() + 1 + val.len();
self.ioctl(IoctlType::Set, IOCTL_CMD_SET_VAR, 0, &mut buf[..total_len])
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!("get {}", name);
debug!("iovar get {}", name);
let mut buf = [0; 64];
buf[..name.len()].copy_from_slice(name.as_bytes());
@ -510,7 +585,7 @@ impl<'a> Control<'a> {
let total_len = max(name.len() + 1, res.len());
let res_len = self
.ioctl(IoctlType::Get, IOCTL_CMD_GET_VAR, 0, &mut buf[..total_len])
.ioctl_inner(IoctlType::Get, Ioctl::GetVar, 0, &mut buf[..total_len])
.await;
let out_len = min(res.len(), res_len);
@ -518,12 +593,20 @@ impl<'a> Control<'a> {
out_len
}
async fn ioctl_set_u32(&mut self, cmd: u32, iface: u32, val: u32) {
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: u32, iface: u32, buf: &mut [u8]) -> usize {
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<'_> {
@ -615,7 +698,7 @@ impl<'a> Control<'a> {
}
/// Leave the wifi, with which we are currently associated.
pub async fn leave(&mut self) {
self.ioctl(IoctlType::Set, IOCTL_CMD_DISASSOC, 0, &mut []).await;
self.ioctl(IoctlType::Set, Ioctl::Disassoc, 0, &mut []).await;
info!("Disassociated")
}

View File

@ -4,9 +4,10 @@ use core::task::{Poll, Waker};
use embassy_sync::waitqueue::WakerRegistration;
use crate::consts::Ioctl;
use crate::fmt::Bytes;
#[derive(Clone, Copy)]
#[derive(Clone, Copy, PartialEq, Eq)]
pub enum IoctlType {
Get = 0,
Set = 2,
@ -16,7 +17,7 @@ pub enum IoctlType {
pub struct PendingIoctl {
pub buf: *mut [u8],
pub kind: IoctlType,
pub cmd: u32,
pub cmd: Ioctl,
pub iface: u32,
}
@ -101,7 +102,7 @@ impl IoctlState {
self.state.set(IoctlStateInner::Done { resp_len: 0 });
}
pub async fn do_ioctl(&self, kind: IoctlType, cmd: u32, iface: u32, buf: &mut [u8]) -> usize {
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();

View File

@ -28,7 +28,9 @@ use ioctl::IoctlState;
use crate::bus::Bus;
pub use crate::bus::SpiBusCyw43;
pub use crate::control::{AddMulticastAddressError, Control, Error as ControlError, ScanOptions, Scanner};
pub use crate::control::{
AddMulticastAddressError, Control, Error as ControlError, JoinAuth, JoinOptions, ScanOptions, Scanner,
};
pub use crate::runner::Runner;
pub use crate::structs::BssInfo;

View File

@ -560,7 +560,7 @@ where
self.sdpcm_seq != self.sdpcm_seq_max && self.sdpcm_seq_max.wrapping_sub(self.sdpcm_seq) & 0x80 == 0
}
async fn send_ioctl(&mut self, kind: IoctlType, cmd: u32, iface: u32, data: &[u8], buf: &mut [u32; 512]) {
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();
@ -582,7 +582,7 @@ where
};
let cdc_header = CdcHeader {
cmd: cmd,
cmd: cmd as u32,
len: data.len() as _,
flags: kind as u16 | (iface as u16) << 12,
id: self.ioctl_id,

View File

@ -394,6 +394,15 @@ pub struct PassphraseInfo {
}
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)]

View File

@ -19,6 +19,8 @@ The bootloader supports
In general, the bootloader works on any platform that implements the `embedded-storage` traits for its internal flash, but may require custom initialization code to work.
STM32L0x1 devices require the `flash-erase-zero` feature to be enabled.
== Design
image::bootloader_flash.png[Bootloader flash layout]
@ -86,8 +88,7 @@ Then, to sign your firmware given a declaration of `FIRMWARE_DIR` and a firmware
[source, bash]
----
shasum -a 512 -b $FIRMWARE_DIR/myfirmware > $SECRETS_DIR/message.txt
cat $SECRETS_DIR/message.txt | dd ibs=128 count=1 | xxd -p -r > $SECRETS_DIR/message.txt
shasum -a 512 -b $FIRMWARE_DIR/myfirmware | head -c128 | xxd -p -r > $SECRETS_DIR/message.txt
signify -S -s $SECRETS_DIR/key.sec -m $SECRETS_DIR/message.txt -x $SECRETS_DIR/message.txt.sig
cp $FIRMWARE_DIR/myfirmware $FIRMWARE_DIR/myfirmware+signed
tail -n1 $SECRETS_DIR/message.txt.sig | base64 -d -i - | dd ibs=10 skip=1 >> $FIRMWARE_DIR/myfirmware+signed

View File

@ -77,3 +77,7 @@ For more reading material on async Rust and Embassy:
* link:https://tweedegolf.nl/en/blog/65/async-rust-vs-rtos-showdown[Comparsion of FreeRTOS and Embassy]
* link:https://dev.to/apollolabsbin/series/20707[Tutorials]
* link:https://blog.drogue.io/firmware-updates-part-1/[Firmware Updates with Embassy]
Videos:
* link:https://www.youtube.com/watch?v=wni5h5vIPhU[From Zero to Async in Embedded Rust]

View File

@ -27,7 +27,7 @@ features = ["defmt"]
defmt = { version = "0.3", optional = true }
digest = "0.10"
log = { version = "0.4", optional = true }
ed25519-dalek = { version = "2", default_features = false, features = ["digest"], optional = true }
ed25519-dalek = { version = "2", default-features = false, features = ["digest"], optional = true }
embassy-embedded-hal = { version = "0.2.0", path = "../embassy-embedded-hal" }
embassy-sync = { version = "0.6.0", path = "../embassy-sync" }
embedded-storage = "0.3.1"
@ -42,11 +42,12 @@ rand = "0.8"
futures = { version = "0.3", features = ["executor"] }
sha1 = "0.10.5"
critical-section = { version = "1.1.1", features = ["std"] }
ed25519-dalek = { version = "2", default_features = false, features = ["std", "rand_core", "digest"] }
ed25519-dalek = { version = "2", default-features = false, features = ["std", "rand_core", "digest"] }
[features]
ed25519-dalek = ["dep:ed25519-dalek", "_verify"]
ed25519-salty = ["dep:salty", "_verify"]
flash-erase-zero = []
#Internal features
_verify = []

View File

@ -5,7 +5,7 @@ use embassy_sync::blocking_mutex::raw::NoopRawMutex;
use embassy_sync::blocking_mutex::Mutex;
use embedded_storage::nor_flash::{NorFlash, NorFlashError, NorFlashErrorKind};
use crate::{State, BOOT_MAGIC, DFU_DETACH_MAGIC, STATE_ERASE_VALUE, SWAP_MAGIC};
use crate::{State, DFU_DETACH_MAGIC, REVERT_MAGIC, STATE_ERASE_VALUE, SWAP_MAGIC};
/// Errors returned by bootloader
#[derive(PartialEq, Eq, Debug)]
@ -276,7 +276,7 @@ impl<ACTIVE: NorFlash, DFU: NorFlash, STATE: NorFlash> BootLoader<ACTIVE, DFU, S
self.state.erase(0, self.state.capacity() as u32)?;
// Set magic
state_word.fill(BOOT_MAGIC);
state_word.fill(REVERT_MAGIC);
self.state.write(0, state_word)?;
}
}
@ -411,6 +411,8 @@ impl<ACTIVE: NorFlash, DFU: NorFlash, STATE: NorFlash> BootLoader<ACTIVE, DFU, S
Ok(State::Swap)
} else if !state_word.iter().any(|&b| b != DFU_DETACH_MAGIC) {
Ok(State::DfuDetach)
} else if !state_word.iter().any(|&b| b != REVERT_MAGIC) {
Ok(State::Revert)
} else {
Ok(State::Boot)
}

View File

@ -107,7 +107,8 @@ impl<'d, DFU: NorFlash, STATE: NorFlash> FirmwareUpdater<'d, DFU, STATE> {
let mut message = [0; 64];
self.hash::<Sha512>(_update_len, &mut chunk_buf, &mut message).await?;
public_key.verify(&message, &signature).map_err(into_signature_error)?
public_key.verify(&message, &signature).map_err(into_signature_error)?;
return self.state.mark_updated().await;
}
#[cfg(feature = "ed25519-salty")]
{
@ -134,10 +135,13 @@ impl<'d, DFU: NorFlash, STATE: NorFlash> FirmwareUpdater<'d, DFU, STATE> {
message,
r.is_ok()
);
r.map_err(into_signature_error)?
r.map_err(into_signature_error)?;
return self.state.mark_updated().await;
}
#[cfg(not(any(feature = "ed25519-dalek", feature = "ed25519-salty")))]
{
Err(FirmwareUpdaterError::Signature(signature::Error::new()))
}
self.state.mark_updated().await
}
/// Verify the update in DFU with any digest.
@ -285,7 +289,8 @@ impl<'d, STATE: NorFlash> FirmwareState<'d, STATE> {
// Make sure we are running a booted firmware to avoid reverting to a bad state.
async fn verify_booted(&mut self) -> Result<(), FirmwareUpdaterError> {
if self.get_state().await? == State::Boot {
let state = self.get_state().await?;
if state == State::Boot || state == State::DfuDetach || state == State::Revert {
Ok(())
} else {
Err(FirmwareUpdaterError::BadState)
@ -299,12 +304,7 @@ impl<'d, STATE: NorFlash> FirmwareState<'d, STATE> {
/// `mark_booted`.
pub async fn get_state(&mut self) -> Result<State, FirmwareUpdaterError> {
self.state.read(0, &mut self.aligned).await?;
if !self.aligned.iter().any(|&b| b != SWAP_MAGIC) {
Ok(State::Swap)
} else {
Ok(State::Boot)
}
Ok(State::from(&self.aligned))
}
/// Mark to trigger firmware swap on next boot.

View File

@ -142,7 +142,8 @@ impl<'d, DFU: NorFlash, STATE: NorFlash> BlockingFirmwareUpdater<'d, DFU, STATE>
let mut chunk_buf = [0; 2];
self.hash::<Sha512>(_update_len, &mut chunk_buf, &mut message)?;
public_key.verify(&message, &signature).map_err(into_signature_error)?
public_key.verify(&message, &signature).map_err(into_signature_error)?;
return self.state.mark_updated();
}
#[cfg(feature = "ed25519-salty")]
{
@ -169,10 +170,13 @@ impl<'d, DFU: NorFlash, STATE: NorFlash> BlockingFirmwareUpdater<'d, DFU, STATE>
message,
r.is_ok()
);
r.map_err(into_signature_error)?
r.map_err(into_signature_error)?;
return self.state.mark_updated();
}
#[cfg(not(any(feature = "ed25519-dalek", feature = "ed25519-salty")))]
{
Err(FirmwareUpdaterError::Signature(signature::Error::new()))
}
self.state.mark_updated()
}
/// Verify the update in DFU with any digest.
@ -320,7 +324,8 @@ impl<'d, STATE: NorFlash> BlockingFirmwareState<'d, STATE> {
// Make sure we are running a booted firmware to avoid reverting to a bad state.
fn verify_booted(&mut self) -> Result<(), FirmwareUpdaterError> {
if self.get_state()? == State::Boot || self.get_state()? == State::DfuDetach {
let state = self.get_state()?;
if state == State::Boot || state == State::DfuDetach || state == State::Revert {
Ok(())
} else {
Err(FirmwareUpdaterError::BadState)
@ -334,14 +339,7 @@ impl<'d, STATE: NorFlash> BlockingFirmwareState<'d, STATE> {
/// `mark_booted`.
pub fn get_state(&mut self) -> Result<State, FirmwareUpdaterError> {
self.state.read(0, &mut self.aligned)?;
if !self.aligned.iter().any(|&b| b != SWAP_MAGIC) {
Ok(State::Swap)
} else if !self.aligned.iter().any(|&b| b != DFU_DETACH_MAGIC) {
Ok(State::DfuDetach)
} else {
Ok(State::Boot)
}
Ok(State::from(&self.aligned))
}
/// Mark to trigger firmware swap on next boot.

View File

@ -14,13 +14,18 @@ mod test_flash;
// The expected value of the flash after an erase
// TODO: Use the value provided by NorFlash when available
#[cfg(not(feature = "flash-erase-zero"))]
pub(crate) const STATE_ERASE_VALUE: u8 = 0xFF;
#[cfg(feature = "flash-erase-zero")]
pub(crate) const STATE_ERASE_VALUE: u8 = 0x00;
pub use boot_loader::{BootError, BootLoader, BootLoaderConfig};
pub use firmware_updater::{
BlockingFirmwareState, BlockingFirmwareUpdater, FirmwareState, FirmwareUpdater, FirmwareUpdaterConfig,
FirmwareUpdaterError,
};
pub(crate) const REVERT_MAGIC: u8 = 0xC0;
pub(crate) const BOOT_MAGIC: u8 = 0xD0;
pub(crate) const SWAP_MAGIC: u8 = 0xF0;
pub(crate) const DFU_DETACH_MAGIC: u8 = 0xE0;
@ -33,10 +38,30 @@ pub enum State {
Boot,
/// Bootloader has swapped the active partition with the dfu partition and will attempt boot.
Swap,
/// Bootloader has reverted the active partition with the dfu partition and will attempt boot.
Revert,
/// Application has received a request to reboot into DFU mode to apply an update.
DfuDetach,
}
impl<T> From<T> for State
where
T: AsRef<[u8]>,
{
fn from(magic: T) -> State {
let magic = magic.as_ref();
if !magic.iter().any(|&b| b != SWAP_MAGIC) {
State::Swap
} else if !magic.iter().any(|&b| b != REVERT_MAGIC) {
State::Revert
} else if !magic.iter().any(|&b| b != DFU_DETACH_MAGIC) {
State::DfuDetach
} else {
State::Boot
}
}
}
/// Buffer aligned to 32 byte boundary, largest known alignment requirement for embassy-boot.
#[repr(align(32))]
pub struct AlignedBuffer<const N: usize>(pub [u8; N]);
@ -153,6 +178,9 @@ mod tests {
// Running again should cause a revert
assert_eq!(State::Swap, bootloader.prepare_boot(&mut page).unwrap());
// Next time we know it was reverted
assert_eq!(State::Revert, bootloader.prepare_boot(&mut page).unwrap());
let mut read_buf = [0; FIRMWARE_SIZE];
flash.active().read(0, &mut read_buf).unwrap();
assert_eq!(ORIGINAL, read_buf);

View File

@ -79,7 +79,7 @@ arch-cortex-m = ["_arch", "dep:cortex-m"]
## RISC-V 32
arch-riscv32 = ["_arch"]
## WASM
arch-wasm = ["_arch", "dep:wasm-bindgen", "dep:js-sys"]
arch-wasm = ["_arch", "dep:wasm-bindgen", "dep:js-sys", "critical-section/std"]
## AVR
arch-avr = ["_arch", "dep:portable-atomic", "dep:avr-device"]

View File

@ -1,5 +1,4 @@
#![cfg_attr(not(any(feature = "arch-std", feature = "arch-wasm")), no_std)]
#![cfg_attr(feature = "nightly", feature(waker_getters))]
#![allow(clippy::new_without_default)]
#![doc = include_str!("../README.md")]
#![warn(missing_docs)]

View File

@ -50,8 +50,7 @@ pub fn task_from_waker(waker: &Waker) -> TaskRef {
#[cfg(feature = "nightly")]
{
let raw_waker = waker.as_raw();
(raw_waker.vtable(), raw_waker.data())
(waker.vtable(), waker.data())
}
};

View File

@ -237,7 +237,7 @@ impl<Fut: Future, const N: usize> Future for SelectArray<Fut, N> {
#[derive(Debug)]
#[must_use = "futures do nothing unless you `.await` or poll them"]
pub struct SelectSlice<'a, Fut> {
inner: &'a mut [Fut],
inner: Pin<&'a mut [Fut]>,
}
/// Creates a new future which will select over a slice of futures.
@ -247,31 +247,26 @@ pub struct SelectSlice<'a, Fut> {
/// future that was ready.
///
/// If the slice is empty, the resulting future will be Pending forever.
pub fn select_slice<'a, Fut: Future>(slice: &'a mut [Fut]) -> SelectSlice<'a, Fut> {
pub fn select_slice<'a, Fut: Future>(slice: Pin<&'a mut [Fut]>) -> SelectSlice<'a, Fut> {
SelectSlice { inner: slice }
}
impl<'a, Fut: Future> Future for SelectSlice<'a, Fut> {
type Output = (Fut::Output, usize);
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
// Safety: Since `self` is pinned, `inner` cannot move. Since `inner` cannot move,
// its elements also cannot move. Therefore it is safe to access `inner` and pin
// references to the contained futures.
let item = unsafe {
self.get_unchecked_mut()
.inner
.iter_mut()
.enumerate()
.find_map(|(i, f)| match Pin::new_unchecked(f).poll(cx) {
Poll::Pending => None,
Poll::Ready(e) => Some((i, e)),
})
};
match item {
Some((idx, res)) => Poll::Ready((res, idx)),
None => Poll::Pending,
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
// Safety: refer to
// https://users.rust-lang.org/t/working-with-pinned-slices-are-there-any-structurally-pinning-vec-like-collection-types/50634/2
#[inline(always)]
fn pin_iter<T>(slice: Pin<&mut [T]>) -> impl Iterator<Item = Pin<&mut T>> {
unsafe { slice.get_unchecked_mut().iter_mut().map(|v| Pin::new_unchecked(v)) }
}
for (i, fut) in pin_iter(self.inner.as_mut()).enumerate() {
if let Poll::Ready(res) = fut.poll(cx) {
return Poll::Ready((res, i));
}
}
Poll::Pending
}
}

View File

@ -120,7 +120,7 @@ impl<'a> Control<'a> {
pwd: unwrap!(String::try_from(password)),
bssid: String::new(),
listen_interval: 3,
is_wpa3_supported: false,
is_wpa3_supported: true,
};
ioctl!(self, ReqConnectAp, RespConnectAp, req, resp);
self.state_ch.set_link_state(LinkState::Up);

View File

@ -137,7 +137,7 @@ where
let (ch_runner, device) = ch::new(&mut state.ch, ch::driver::HardwareAddress::Ethernet([0; 6]));
let state_ch = ch_runner.state_runner();
let mut runner = Runner {
let runner = Runner {
ch: ch_runner,
state_ch,
shared: &state.shared,
@ -148,7 +148,6 @@ where
spi,
heartbeat_deadline: Instant::now() + HEARTBEAT_MAX_GAP,
};
runner.init().await;
(device, Control::new(state_ch, &state.shared), runner)
}
@ -174,8 +173,6 @@ where
IN: InputPin + Wait,
OUT: OutputPin,
{
async fn init(&mut self) {}
/// Run the packet processing.
pub async fn run(mut self) -> ! {
debug!("resetting...");

View File

@ -0,0 +1,38 @@
[package]
name = "embassy-net-nrf91"
version = "0.1.0"
edition = "2021"
description = "embassy-net driver for Nordic nRF91-series cellular modems"
keywords = ["embedded", "nrf91", "embassy-net", "cellular"]
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/embassy-net-nrf91"
[features]
defmt = [ "dep:defmt", "heapless/defmt-03" ]
log = [ "dep:log" ]
[dependencies]
defmt = { version = "0.3", optional = true }
log = { version = "0.4.14", optional = true }
nrf9160-pac = { version = "0.12.0" }
embassy-time = { version = "0.3.1", path = "../embassy-time" }
embassy-sync = { version = "0.6.0", 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"}
heapless = "0.8"
embedded-io = "0.6.1"
at-commands = "0.5.4"
[package.metadata.embassy_docs]
src_base = "https://github.com/embassy-rs/embassy/blob/embassy-net-nrf91-v$VERSION/embassy-net-nrf91/src/"
src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-net-nrf91/src/"
target = "thumbv7em-none-eabi"
features = ["defmt"]
[package.metadata.docs.rs]
features = ["defmt"]

View File

@ -0,0 +1,9 @@
# nRF91 `embassy-net` integration
[`embassy-net`](https://crates.io/crates/embassy-net) driver for Nordic nRF91-series cellular modems.
See the [`examples`](https://github.com/embassy-rs/embassy/tree/main/examples/nrf9160) directory for usage examples with the nRF9160.
## Interoperability
This crate can run on any executor.

View File

@ -0,0 +1,350 @@
//! Helper utility to configure a specific modem context.
use core::net::IpAddr;
use core::str::FromStr;
use at_commands::builder::CommandBuilder;
use at_commands::parser::CommandParser;
use embassy_time::{Duration, Timer};
use heapless::Vec;
/// Provides a higher level API for controlling a given context.
pub struct Control<'a> {
control: crate::Control<'a>,
cid: u8,
}
/// Configuration for a given context
pub struct Config<'a> {
/// Desired APN address.
pub apn: &'a [u8],
/// Desired authentication protocol.
pub auth_prot: AuthProt,
/// Credentials.
pub auth: Option<(&'a [u8], &'a [u8])>,
}
/// Authentication protocol.
#[derive(Clone, Copy, PartialEq, Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[repr(u8)]
pub enum AuthProt {
/// No authentication.
None = 0,
/// PAP authentication.
Pap = 1,
/// CHAP authentication.
Chap = 2,
}
/// Error returned by control.
#[derive(Clone, Copy, PartialEq, Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum Error {
/// Not enough space for command.
BufferTooSmall,
/// Error parsing response from modem.
AtParseError,
/// Error parsing IP addresses.
AddrParseError,
}
impl From<at_commands::parser::ParseError> for Error {
fn from(_: at_commands::parser::ParseError) -> Self {
Self::AtParseError
}
}
/// Status of a given context.
#[derive(PartialEq, Debug)]
pub struct Status {
/// Attached to APN or not.
pub attached: bool,
/// IP if assigned.
pub ip: Option<IpAddr>,
/// Gateway if assigned.
pub gateway: Option<IpAddr>,
/// DNS servers if assigned.
pub dns: Vec<IpAddr, 2>,
}
#[cfg(feature = "defmt")]
impl defmt::Format for Status {
fn format(&self, f: defmt::Formatter<'_>) {
defmt::write!(f, "attached: {}", self.attached);
if let Some(ip) = &self.ip {
defmt::write!(f, ", ip: {}", defmt::Debug2Format(&ip));
}
}
}
impl<'a> Control<'a> {
/// Create a new instance of a control handle for a given context.
///
/// Will wait for the modem to be initialized if not.
pub async fn new(control: crate::Control<'a>, cid: u8) -> Self {
control.wait_init().await;
Self { control, cid }
}
/// Perform a raw AT command
pub async fn at_command(&self, req: &[u8], resp: &mut [u8]) -> usize {
self.control.at_command(req, resp).await
}
/// Configures the modem with the provided config.
///
/// NOTE: This will disconnect the modem from any current APN and should not
/// be called if the configuration has not been changed.
///
/// After configuring, invoke [`enable()`] to activate the configuration.
pub async fn configure(&self, config: &Config<'_>) -> Result<(), Error> {
let mut cmd: [u8; 256] = [0; 256];
let mut buf: [u8; 256] = [0; 256];
let op = CommandBuilder::create_set(&mut cmd, true)
.named("+CFUN")
.with_int_parameter(0)
.finish()
.map_err(|_| Error::BufferTooSmall)?;
let n = self.control.at_command(op, &mut buf).await;
CommandParser::parse(&buf[..n]).expect_identifier(b"OK").finish()?;
let op = CommandBuilder::create_set(&mut cmd, true)
.named("+CGDCONT")
.with_int_parameter(self.cid)
.with_string_parameter("IP")
.with_string_parameter(config.apn)
.finish()
.map_err(|_| Error::BufferTooSmall)?;
let n = self.control.at_command(op, &mut buf).await;
// info!("RES1: {}", unsafe { core::str::from_utf8_unchecked(&buf[..n]) });
CommandParser::parse(&buf[..n]).expect_identifier(b"OK").finish()?;
let mut op = CommandBuilder::create_set(&mut cmd, true)
.named("+CGAUTH")
.with_int_parameter(self.cid)
.with_int_parameter(config.auth_prot as u8);
if let Some((username, password)) = config.auth {
op = op.with_string_parameter(username).with_string_parameter(password);
}
let op = op.finish().map_err(|_| Error::BufferTooSmall)?;
let n = self.control.at_command(op, &mut buf).await;
// info!("RES2: {}", unsafe { core::str::from_utf8_unchecked(&buf[..n]) });
CommandParser::parse(&buf[..n]).expect_identifier(b"OK").finish()?;
Ok(())
}
/// Attach to the PDN
pub async fn attach(&self) -> Result<(), Error> {
let mut cmd: [u8; 256] = [0; 256];
let mut buf: [u8; 256] = [0; 256];
let op = CommandBuilder::create_set(&mut cmd, true)
.named("+CGATT")
.with_int_parameter(1)
.finish()
.map_err(|_| Error::BufferTooSmall)?;
let n = self.control.at_command(op, &mut buf).await;
CommandParser::parse(&buf[..n]).expect_identifier(b"OK").finish()?;
Ok(())
}
/// Read current connectivity status for modem.
pub async fn detach(&self) -> Result<(), Error> {
let mut cmd: [u8; 256] = [0; 256];
let mut buf: [u8; 256] = [0; 256];
let op = CommandBuilder::create_set(&mut cmd, true)
.named("+CGATT")
.with_int_parameter(0)
.finish()
.map_err(|_| Error::BufferTooSmall)?;
let n = self.control.at_command(op, &mut buf).await;
CommandParser::parse(&buf[..n]).expect_identifier(b"OK").finish()?;
Ok(())
}
async fn attached(&self) -> Result<bool, Error> {
let mut cmd: [u8; 256] = [0; 256];
let mut buf: [u8; 256] = [0; 256];
let op = CommandBuilder::create_query(&mut cmd, true)
.named("+CGATT")
.finish()
.map_err(|_| Error::BufferTooSmall)?;
let n = self.control.at_command(op, &mut buf).await;
let (res,) = CommandParser::parse(&buf[..n])
.expect_identifier(b"+CGATT: ")
.expect_int_parameter()
.expect_identifier(b"\r\nOK")
.finish()?;
Ok(res == 1)
}
/// Read current connectivity status for modem.
pub async fn status(&self) -> Result<Status, Error> {
let mut cmd: [u8; 256] = [0; 256];
let mut buf: [u8; 256] = [0; 256];
let op = CommandBuilder::create_query(&mut cmd, true)
.named("+CGATT")
.finish()
.map_err(|_| Error::BufferTooSmall)?;
let n = self.control.at_command(op, &mut buf).await;
let (res,) = CommandParser::parse(&buf[..n])
.expect_identifier(b"+CGATT: ")
.expect_int_parameter()
.expect_identifier(b"\r\nOK")
.finish()?;
let attached = res == 1;
if !attached {
return Ok(Status {
attached,
ip: None,
gateway: None,
dns: Vec::new(),
});
}
let op = CommandBuilder::create_set(&mut cmd, true)
.named("+CGPADDR")
.with_int_parameter(self.cid)
.finish()
.map_err(|_| Error::BufferTooSmall)?;
let n = self.control.at_command(op, &mut buf).await;
let (_, ip1, _ip2) = CommandParser::parse(&buf[..n])
.expect_identifier(b"+CGPADDR: ")
.expect_int_parameter()
.expect_optional_string_parameter()
.expect_optional_string_parameter()
.expect_identifier(b"\r\nOK")
.finish()?;
let ip = if let Some(ip) = ip1 {
let ip = IpAddr::from_str(ip).map_err(|_| Error::AddrParseError)?;
Some(ip)
} else {
None
};
let op = CommandBuilder::create_set(&mut cmd, true)
.named("+CGCONTRDP")
.with_int_parameter(self.cid)
.finish()
.map_err(|_| Error::BufferTooSmall)?;
let n = self.control.at_command(op, &mut buf).await;
let (_cid, _bid, _apn, _mask, gateway, dns1, dns2, _, _, _, _, _mtu) = CommandParser::parse(&buf[..n])
.expect_identifier(b"+CGCONTRDP: ")
.expect_int_parameter()
.expect_optional_int_parameter()
.expect_optional_string_parameter()
.expect_optional_string_parameter()
.expect_optional_string_parameter()
.expect_optional_string_parameter()
.expect_optional_string_parameter()
.expect_optional_int_parameter()
.expect_optional_int_parameter()
.expect_optional_int_parameter()
.expect_optional_int_parameter()
.expect_optional_int_parameter()
.expect_identifier(b"\r\nOK")
.finish()?;
let gateway = if let Some(ip) = gateway {
if ip.is_empty() {
None
} else {
Some(IpAddr::from_str(ip).map_err(|_| Error::AddrParseError)?)
}
} else {
None
};
let mut dns = Vec::new();
if let Some(ip) = dns1 {
dns.push(IpAddr::from_str(ip).map_err(|_| Error::AddrParseError)?)
.unwrap();
}
if let Some(ip) = dns2 {
dns.push(IpAddr::from_str(ip).map_err(|_| Error::AddrParseError)?)
.unwrap();
}
Ok(Status {
attached,
ip,
gateway,
dns,
})
}
async fn wait_attached(&self) -> Result<Status, Error> {
while !self.attached().await? {
Timer::after(Duration::from_secs(1)).await;
}
let status = self.status().await?;
Ok(status)
}
/// Disable modem
pub async fn disable(&self) -> Result<(), Error> {
let mut cmd: [u8; 256] = [0; 256];
let mut buf: [u8; 256] = [0; 256];
let op = CommandBuilder::create_set(&mut cmd, true)
.named("+CFUN")
.with_int_parameter(0)
.finish()
.map_err(|_| Error::BufferTooSmall)?;
let n = self.control.at_command(op, &mut buf).await;
CommandParser::parse(&buf[..n]).expect_identifier(b"OK").finish()?;
Ok(())
}
/// Enable modem
pub async fn enable(&self) -> Result<(), Error> {
let mut cmd: [u8; 256] = [0; 256];
let mut buf: [u8; 256] = [0; 256];
let op = CommandBuilder::create_set(&mut cmd, true)
.named("+CFUN")
.with_int_parameter(1)
.finish()
.map_err(|_| Error::BufferTooSmall)?;
let n = self.control.at_command(op, &mut buf).await;
CommandParser::parse(&buf[..n]).expect_identifier(b"OK").finish()?;
// Make modem survive PDN detaches
let op = CommandBuilder::create_set(&mut cmd, true)
.named("%XPDNCFG")
.with_int_parameter(1)
.finish()
.map_err(|_| Error::BufferTooSmall)?;
let n = self.control.at_command(op, &mut buf).await;
CommandParser::parse(&buf[..n]).expect_identifier(b"OK").finish()?;
Ok(())
}
/// Run a control loop for this context, ensuring that reaattach is handled.
pub async fn run<F: Fn(&Status)>(&self, reattach: F) -> Result<(), Error> {
self.enable().await?;
let status = self.wait_attached().await?;
let mut fd = self.control.open_raw_socket().await;
reattach(&status);
loop {
if !self.attached().await? {
trace!("detached");
self.control.close_raw_socket(fd).await;
let status = self.wait_attached().await?;
trace!("attached");
fd = self.control.open_raw_socket().await;
reattach(&status);
}
Timer::after(Duration::from_secs(10)).await;
}
}
}

View File

@ -0,0 +1,274 @@
#![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)*);
}
};
}
#[cfg(not(feature = "defmt"))]
#[collapse_debuginfo(yes)]
macro_rules! unreachable {
($($x:tt)*) => {
::core::unreachable!($($x)*)
};
}
#[cfg(feature = "defmt")]
#[collapse_debuginfo(yes)]
macro_rules! unreachable {
($($x:tt)*) => {
::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)
}
}

1046
embassy-net-nrf91/src/lib.rs Normal file

File diff suppressed because it is too large Load Diff

View File

@ -16,11 +16,11 @@ categories = [
[package.metadata.embassy_docs]
src_base = "https://github.com/embassy-rs/embassy/blob/embassy-net-v$VERSION/embassy-net/src/"
src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-net/src/"
features = ["defmt", "tcp", "udp", "raw", "dns", "dhcpv4", "proto-ipv6", "medium-ethernet", "medium-ip", "medium-ieee802154", "igmp", "dhcpv4-hostname"]
features = ["defmt", "tcp", "udp", "raw", "dns", "dhcpv4", "proto-ipv6", "medium-ethernet", "medium-ip", "medium-ieee802154", "multicast", "dhcpv4-hostname"]
target = "thumbv7em-none-eabi"
[package.metadata.docs.rs]
features = ["defmt", "tcp", "udp", "raw", "dns", "dhcpv4", "proto-ipv6", "medium-ethernet", "medium-ip", "medium-ieee802154", "igmp", "dhcpv4-hostname"]
features = ["defmt", "tcp", "udp", "raw", "dns", "dhcpv4", "proto-ipv6", "medium-ethernet", "medium-ip", "medium-ieee802154", "multicast", "dhcpv4-hostname"]
[features]
default = []
@ -60,15 +60,15 @@ medium-ethernet = ["smoltcp/medium-ethernet"]
medium-ip = ["smoltcp/medium-ip"]
## Enable the IEEE 802.15.4 medium
medium-ieee802154 = ["smoltcp/medium-ieee802154"]
## Enable IGMP support
igmp = ["smoltcp/proto-igmp"]
## Enable multicast support (for both ipv4 and/or ipv6 if enabled)
multicast = ["smoltcp/multicast"]
[dependencies]
defmt = { version = "0.3", optional = true }
log = { version = "0.4.14", optional = true }
smoltcp = { version = "0.11.0", default-features = false, features = [
smoltcp = { git="https://github.com/smoltcp-rs/smoltcp", rev="dd43c8f189178b0ab3bda798ed8578b5b0a6f094", default-features = false, features = [
"socket",
"async",
] }

View File

@ -10,8 +10,9 @@ memory management designed to work well for embedded systems, aiming for a more
- IPv4, IPv6
- Ethernet and bare-IP mediums.
- TCP, UDP, DNS, DHCPv4, IGMPv4
- TCP, UDP, DNS, DHCPv4
- TCP sockets implement the `embedded-io` async traits.
- Multicast
See the [`smoltcp`](https://github.com/smoltcp-rs/smoltcp) README for a detailed list of implemented and
unimplemented features of the network protocols.

View File

@ -9,7 +9,7 @@ pub use smoltcp::socket::dns::{DnsQuery, Socket};
pub(crate) use smoltcp::socket::dns::{GetQueryResultError, StartQueryError};
pub use smoltcp::wire::{DnsQueryType, IpAddress};
use crate::{Driver, Stack};
use crate::Stack;
/// Errors returned by DnsSocket.
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
@ -44,21 +44,15 @@ impl From<StartQueryError> for Error {
/// This exists only for compatibility with crates that use `embedded-nal-async`.
/// Prefer using [`Stack::dns_query`](crate::Stack::dns_query) directly if you're
/// not using `embedded-nal-async`.
pub struct DnsSocket<'a, D>
where
D: Driver + 'static,
{
stack: &'a Stack<D>,
pub struct DnsSocket<'a> {
stack: Stack<'a>,
}
impl<'a, D> DnsSocket<'a, D>
where
D: Driver + 'static,
{
impl<'a> DnsSocket<'a> {
/// Create a new DNS socket using the provided stack.
///
/// NOTE: If using DHCP, make sure it has reconfigured the stack to ensure the DNS servers are updated.
pub fn new(stack: &'a Stack<D>) -> Self {
pub fn new(stack: Stack<'a>) -> Self {
Self { stack }
}
@ -72,10 +66,7 @@ where
}
}
impl<'a, D> embedded_nal_async::Dns for DnsSocket<'a, D>
where
D: Driver + 'static,
{
impl<'a> embedded_nal_async::Dns for DnsSocket<'a> {
type Error = Error;
async fn get_host_by_name(
@ -124,3 +115,7 @@ where
todo!()
}
}
fn _assert_covariant<'a, 'b: 'a>(x: DnsSocket<'b>) -> DnsSocket<'a> {
x
}

View File

@ -74,7 +74,7 @@ where
{
fn consume<R, F>(self, f: F) -> R
where
F: FnOnce(&mut [u8]) -> R,
F: FnOnce(&[u8]) -> R,
{
self.0.consume(|buf| {
#[cfg(feature = "packet-trace")]

View File

@ -12,9 +12,9 @@ compile_error!("You must enable at least one of the following features: proto-ip
// This mod MUST go first, so that the others see its macros.
pub(crate) mod fmt;
mod device;
#[cfg(feature = "dns")]
pub mod dns;
mod driver_util;
#[cfg(feature = "raw")]
pub mod raw;
#[cfg(feature = "tcp")]
@ -25,6 +25,7 @@ pub mod udp;
use core::cell::RefCell;
use core::future::{poll_fn, Future};
use core::mem::MaybeUninit;
use core::pin::pin;
use core::task::{Context, Poll};
@ -36,7 +37,7 @@ use embassy_time::{Instant, Timer};
use heapless::Vec;
#[cfg(feature = "dns")]
pub use smoltcp::config::DNS_MAX_SERVER_COUNT;
#[cfg(feature = "igmp")]
#[cfg(feature = "multicast")]
pub use smoltcp::iface::MulticastError;
#[allow(unused_imports)]
use smoltcp::iface::{Interface, SocketHandle, SocketSet, SocketStorage};
@ -57,7 +58,7 @@ pub use smoltcp::wire::{Ipv4Address, Ipv4Cidr};
#[cfg(feature = "proto-ipv6")]
pub use smoltcp::wire::{Ipv6Address, Ipv6Cidr};
use crate::device::DriverAdapter;
use crate::driver_util::DriverAdapter;
use crate::time::{instant_from_smoltcp, instant_to_smoltcp};
const LOCAL_PORT_MIN: u16 = 1025;
@ -69,33 +70,33 @@ const MAX_HOSTNAME_LEN: usize = 32;
/// Memory resources needed for a network stack.
pub struct StackResources<const SOCK: usize> {
sockets: [SocketStorage<'static>; SOCK],
sockets: MaybeUninit<[SocketStorage<'static>; SOCK]>,
inner: MaybeUninit<RefCell<Inner>>,
#[cfg(feature = "dns")]
queries: [Option<dns::DnsQuery>; MAX_QUERIES],
queries: MaybeUninit<[Option<dns::DnsQuery>; MAX_QUERIES]>,
#[cfg(feature = "dhcpv4-hostname")]
hostname: core::cell::UnsafeCell<HostnameResources>,
hostname: HostnameResources,
}
#[cfg(feature = "dhcpv4-hostname")]
struct HostnameResources {
option: smoltcp::wire::DhcpOption<'static>,
data: [u8; MAX_HOSTNAME_LEN],
option: MaybeUninit<smoltcp::wire::DhcpOption<'static>>,
data: MaybeUninit<[u8; MAX_HOSTNAME_LEN]>,
}
impl<const SOCK: usize> StackResources<SOCK> {
/// Create a new set of stack resources.
pub const fn new() -> Self {
#[cfg(feature = "dns")]
const INIT: Option<dns::DnsQuery> = None;
Self {
sockets: [SocketStorage::EMPTY; SOCK],
sockets: MaybeUninit::uninit(),
inner: MaybeUninit::uninit(),
#[cfg(feature = "dns")]
queries: [INIT; MAX_QUERIES],
queries: MaybeUninit::uninit(),
#[cfg(feature = "dhcpv4-hostname")]
hostname: core::cell::UnsafeCell::new(HostnameResources {
option: smoltcp::wire::DhcpOption { kind: 0, data: &[] },
data: [0; MAX_HOSTNAME_LEN],
}),
hostname: HostnameResources {
option: MaybeUninit::uninit(),
data: MaybeUninit::uninit(),
},
}
}
}
@ -239,16 +240,32 @@ pub enum ConfigV6 {
Static(StaticConfigV6),
}
/// A network stack.
/// Network stack runner.
///
/// This is the main entry point for the network stack.
pub struct Stack<D: Driver> {
pub(crate) socket: RefCell<SocketStack>,
inner: RefCell<Inner<D>>,
/// You must call [`Runner::run()`] in a background task for the network stack to work.
pub struct Runner<'d, D: Driver> {
driver: D,
stack: Stack<'d>,
}
struct Inner<D: Driver> {
device: D,
/// Network stack handle
///
/// Use this to create sockets. It's `Copy`, so you can pass
/// it by value instead of by reference.
#[derive(Copy, Clone)]
pub struct Stack<'d> {
inner: &'d RefCell<Inner>,
}
pub(crate) struct Inner {
pub(crate) sockets: SocketSet<'static>, // Lifetime type-erased.
pub(crate) iface: Interface,
/// Waker used for triggering polls.
pub(crate) waker: WakerRegistration,
/// Waker used for waiting for link up or config up.
state_waker: WakerRegistration,
hardware_address: HardwareAddress,
next_local_port: u16,
link_up: bool,
#[cfg(feature = "proto-ipv4")]
static_v4: Option<StaticConfigV4>,
@ -256,20 +273,88 @@ struct Inner<D: Driver> {
static_v6: Option<StaticConfigV6>,
#[cfg(feature = "dhcpv4")]
dhcp_socket: Option<SocketHandle>,
config_waker: WakerRegistration,
#[cfg(feature = "dns")]
dns_socket: SocketHandle,
#[cfg(feature = "dns")]
dns_waker: WakerRegistration,
#[cfg(feature = "dhcpv4-hostname")]
hostname: &'static mut core::cell::UnsafeCell<HostnameResources>,
hostname: *mut HostnameResources,
}
pub(crate) struct SocketStack {
pub(crate) sockets: SocketSet<'static>,
pub(crate) iface: Interface,
pub(crate) waker: WakerRegistration,
next_local_port: u16,
fn _assert_covariant<'a, 'b: 'a>(x: Stack<'b>) -> Stack<'a> {
x
}
/// Create a new network stack.
pub fn new<'d, D: Driver, const SOCK: usize>(
mut driver: D,
config: Config,
resources: &'d mut StackResources<SOCK>,
random_seed: u64,
) -> (Stack<'d>, Runner<'d, D>) {
let (hardware_address, medium) = to_smoltcp_hardware_address(driver.hardware_address());
let mut iface_cfg = smoltcp::iface::Config::new(hardware_address);
iface_cfg.random_seed = random_seed;
let iface = Interface::new(
iface_cfg,
&mut DriverAdapter {
inner: &mut driver,
cx: None,
medium,
},
instant_to_smoltcp(Instant::now()),
);
unsafe fn transmute_slice<T>(x: &mut [T]) -> &'static mut [T] {
core::mem::transmute(x)
}
let sockets = resources.sockets.write([SocketStorage::EMPTY; SOCK]);
#[allow(unused_mut)]
let mut sockets: SocketSet<'static> = SocketSet::new(unsafe { transmute_slice(sockets) });
let next_local_port = (random_seed % (LOCAL_PORT_MAX - LOCAL_PORT_MIN) as u64) as u16 + LOCAL_PORT_MIN;
#[cfg(feature = "dns")]
let dns_socket = sockets.add(dns::Socket::new(
&[],
managed::ManagedSlice::Borrowed(unsafe {
transmute_slice(resources.queries.write([const { None }; MAX_QUERIES]))
}),
));
let mut inner = Inner {
sockets,
iface,
waker: WakerRegistration::new(),
state_waker: WakerRegistration::new(),
next_local_port,
hardware_address,
link_up: false,
#[cfg(feature = "proto-ipv4")]
static_v4: None,
#[cfg(feature = "proto-ipv6")]
static_v6: None,
#[cfg(feature = "dhcpv4")]
dhcp_socket: None,
#[cfg(feature = "dns")]
dns_socket,
#[cfg(feature = "dns")]
dns_waker: WakerRegistration::new(),
#[cfg(feature = "dhcpv4-hostname")]
hostname: &mut resources.hostname,
};
#[cfg(feature = "proto-ipv4")]
inner.set_config_v4(config.ipv4);
#[cfg(feature = "proto-ipv6")]
inner.set_config_v6(config.ipv6);
inner.apply_static_config();
let inner = &*resources.inner.write(RefCell::new(inner));
let stack = Stack { inner };
(stack, Runner { driver, stack })
}
fn to_smoltcp_hardware_address(addr: driver::HardwareAddress) -> (HardwareAddress, Medium) {
@ -292,89 +377,23 @@ fn to_smoltcp_hardware_address(addr: driver::HardwareAddress) -> (HardwareAddres
}
}
impl<D: Driver> Stack<D> {
/// Create a new network stack.
pub fn new<const SOCK: usize>(
mut device: D,
config: Config,
resources: &'static mut StackResources<SOCK>,
random_seed: u64,
) -> Self {
let (hardware_addr, medium) = to_smoltcp_hardware_address(device.hardware_address());
let mut iface_cfg = smoltcp::iface::Config::new(hardware_addr);
iface_cfg.random_seed = random_seed;
let iface = Interface::new(
iface_cfg,
&mut DriverAdapter {
inner: &mut device,
cx: None,
medium,
},
instant_to_smoltcp(Instant::now()),
);
let sockets = SocketSet::new(&mut resources.sockets[..]);
let next_local_port = (random_seed % (LOCAL_PORT_MAX - LOCAL_PORT_MIN) as u64) as u16 + LOCAL_PORT_MIN;
#[cfg_attr(feature = "medium-ieee802154", allow(unused_mut))]
let mut socket = SocketStack {
sockets,
iface,
waker: WakerRegistration::new(),
next_local_port,
};
let mut inner = Inner {
device,
link_up: false,
#[cfg(feature = "proto-ipv4")]
static_v4: None,
#[cfg(feature = "proto-ipv6")]
static_v6: None,
#[cfg(feature = "dhcpv4")]
dhcp_socket: None,
config_waker: WakerRegistration::new(),
#[cfg(feature = "dns")]
dns_socket: socket.sockets.add(dns::Socket::new(
&[],
managed::ManagedSlice::Borrowed(&mut resources.queries),
)),
#[cfg(feature = "dns")]
dns_waker: WakerRegistration::new(),
#[cfg(feature = "dhcpv4-hostname")]
hostname: &mut resources.hostname,
};
#[cfg(feature = "proto-ipv4")]
inner.set_config_v4(&mut socket, config.ipv4);
#[cfg(feature = "proto-ipv6")]
inner.set_config_v6(&mut socket, config.ipv6);
inner.apply_static_config(&mut socket);
Self {
socket: RefCell::new(socket),
inner: RefCell::new(inner),
}
impl<'d> Stack<'d> {
fn with<R>(&self, f: impl FnOnce(&Inner) -> R) -> R {
f(&*self.inner.borrow())
}
fn with<R>(&self, f: impl FnOnce(&SocketStack, &Inner<D>) -> R) -> R {
f(&*self.socket.borrow(), &*self.inner.borrow())
}
fn with_mut<R>(&self, f: impl FnOnce(&mut SocketStack, &mut Inner<D>) -> R) -> R {
f(&mut *self.socket.borrow_mut(), &mut *self.inner.borrow_mut())
fn with_mut<R>(&self, f: impl FnOnce(&mut Inner) -> R) -> R {
f(&mut *self.inner.borrow_mut())
}
/// Get the hardware address of the network interface.
pub fn hardware_address(&self) -> HardwareAddress {
self.with(|_s, i| to_smoltcp_hardware_address(i.device.hardware_address()).0)
self.with(|i| i.hardware_address)
}
/// Get whether the link is up.
pub fn is_link_up(&self) -> bool {
self.with(|_s, i| i.link_up)
self.with(|i| i.link_up)
}
/// Get whether the network stack has a valid IP configuration.
@ -404,10 +423,20 @@ impl<D: Driver> Stack<D> {
v4_up || v6_up
}
/// Wait for the network device to obtain a link signal.
pub async fn wait_link_up(&self) {
self.wait(|| self.is_link_up()).await
}
/// Wait for the network device to lose link signal.
pub async fn wait_link_down(&self) {
self.wait(|| !self.is_link_up()).await
}
/// Wait for the network stack to obtain a valid IP configuration.
///
/// ## Notes:
/// - Ensure [`Stack::run`] has been called before using this function.
/// - Ensure [`Runner::run`] has been started before using this function.
///
/// - This function may never return (e.g. if no configuration is obtained through DHCP).
/// The caller is supposed to handle a timeout for this case.
@ -420,42 +449,44 @@ impl<D: Driver> Stack<D> {
/// // provisioning space for 3 sockets here: one for DHCP, one for DNS, and one for your code (e.g. TCP).
/// // If you use more sockets you must increase this. If you don't enable DHCP or DNS you can decrease it.
/// static RESOURCES: StaticCell<embassy_net::StackResources<3>> = StaticCell::new();
/// static STACK: StaticCell<embassy_net::Stack> = StaticCell::new();
/// let stack = &*STACK.init(embassy_net::Stack::new(
/// device,
/// let (stack, runner) = embassy_net::new(
/// driver,
/// config,
/// RESOURCES.init(embassy_net::StackResources::new()),
/// seed
/// ));
/// // Launch network task that runs `stack.run().await`
/// spawner.spawn(net_task(stack)).unwrap();
/// );
/// // Launch network task that runs `runner.run().await`
/// spawner.spawn(net_task(runner)).unwrap();
/// // Wait for DHCP config
/// stack.wait_config_up().await;
/// // use the network stack
/// // ...
/// ```
pub async fn wait_config_up(&self) {
// If the config is up already, we can return immediately.
if self.is_config_up() {
return;
}
self.wait(|| self.is_config_up()).await
}
poll_fn(|cx| {
if self.is_config_up() {
/// Wait for the network stack to lose a valid IP configuration.
pub async fn wait_config_down(&self) {
self.wait(|| !self.is_config_up()).await
}
fn wait<'a>(&'a self, mut predicate: impl FnMut() -> bool + 'a) -> impl Future<Output = ()> + 'a {
poll_fn(move |cx| {
if predicate() {
Poll::Ready(())
} else {
// If the config is not up, we register a waker that is woken up
// when a config is applied (static or DHCP).
trace!("Waiting for config up");
self.with_mut(|_, i| {
i.config_waker.register(cx.waker());
self.with_mut(|i| {
i.state_waker.register(cx.waker());
});
Poll::Pending
}
})
.await;
}
/// Get the current IPv4 configuration.
@ -464,45 +495,33 @@ impl<D: Driver> Stack<D> {
/// acquire an IP address, or Some if it has.
#[cfg(feature = "proto-ipv4")]
pub fn config_v4(&self) -> Option<StaticConfigV4> {
self.with(|_, i| i.static_v4.clone())
self.with(|i| i.static_v4.clone())
}
/// Get the current IPv6 configuration.
#[cfg(feature = "proto-ipv6")]
pub fn config_v6(&self) -> Option<StaticConfigV6> {
self.with(|_, i| i.static_v6.clone())
self.with(|i| i.static_v6.clone())
}
/// Set the IPv4 configuration.
#[cfg(feature = "proto-ipv4")]
pub fn set_config_v4(&self, config: ConfigV4) {
self.with_mut(|s, i| {
i.set_config_v4(s, config);
i.apply_static_config(s);
self.with_mut(|i| {
i.set_config_v4(config);
i.apply_static_config();
})
}
/// Set the IPv6 configuration.
#[cfg(feature = "proto-ipv6")]
pub fn set_config_v6(&self, config: ConfigV6) {
self.with_mut(|s, i| {
i.set_config_v6(s, config);
i.apply_static_config(s);
self.with_mut(|i| {
i.set_config_v6(config);
i.apply_static_config();
})
}
/// Run the network stack.
///
/// You must call this in a background task, to process network events.
pub async fn run(&self) -> ! {
poll_fn(|cx| {
self.with_mut(|s, i| i.poll(cx, s));
Poll::<()>::Pending
})
.await;
unreachable!()
}
/// Make a query for a given name and return the corresponding IP addresses.
#[cfg(feature = "dns")]
pub async fn dns_query(
@ -528,11 +547,11 @@ impl<D: Driver> Stack<D> {
}
let query = poll_fn(|cx| {
self.with_mut(|s, i| {
let socket = s.sockets.get_mut::<dns::Socket>(i.dns_socket);
match socket.start_query(s.iface.context(), name, qtype) {
self.with_mut(|i| {
let socket = i.sockets.get_mut::<dns::Socket>(i.dns_socket);
match socket.start_query(i.iface.context(), name, qtype) {
Ok(handle) => {
s.waker.wake();
i.waker.wake();
Poll::Ready(Ok(handle))
}
Err(dns::StartQueryError::NoFreeSlot) => {
@ -569,17 +588,17 @@ impl<D: Driver> Stack<D> {
}
let drop = OnDrop::new(|| {
self.with_mut(|s, i| {
let socket = s.sockets.get_mut::<dns::Socket>(i.dns_socket);
self.with_mut(|i| {
let socket = i.sockets.get_mut::<dns::Socket>(i.dns_socket);
socket.cancel_query(query);
s.waker.wake();
i.waker.wake();
i.dns_waker.wake();
})
});
let res = poll_fn(|cx| {
self.with_mut(|s, i| {
let socket = s.sockets.get_mut::<dns::Socket>(i.dns_socket);
self.with_mut(|i| {
let socket = i.sockets.get_mut::<dns::Socket>(i.dns_socket);
match socket.get_query_result(query) {
Ok(addrs) => {
i.dns_waker.wake();
@ -604,104 +623,34 @@ impl<D: Driver> Stack<D> {
}
}
#[cfg(feature = "igmp")]
impl<D: Driver> Stack<D> {
#[cfg(feature = "multicast")]
impl<'d> Stack<'d> {
/// Join a multicast group.
pub async fn join_multicast_group<T>(&self, addr: T) -> Result<bool, MulticastError>
where
T: Into<IpAddress>,
{
let addr = addr.into();
poll_fn(move |cx| self.poll_join_multicast_group(addr, cx)).await
}
/// Join a multicast group.
///
/// When the send queue is full, this method will return `Poll::Pending`
/// and register the current task to be notified when the queue has space available.
pub fn poll_join_multicast_group<T>(&self, addr: T, cx: &mut Context<'_>) -> Poll<Result<bool, MulticastError>>
where
T: Into<IpAddress>,
{
let addr = addr.into();
self.with_mut(|s, i| {
let (_hardware_addr, medium) = to_smoltcp_hardware_address(i.device.hardware_address());
let mut smoldev = DriverAdapter {
cx: Some(cx),
inner: &mut i.device,
medium,
};
match s
.iface
.join_multicast_group(&mut smoldev, addr, instant_to_smoltcp(Instant::now()))
{
Ok(announce_sent) => Poll::Ready(Ok(announce_sent)),
Err(MulticastError::Exhausted) => Poll::Pending,
Err(other) => Poll::Ready(Err(other)),
}
})
pub fn join_multicast_group(&self, addr: impl Into<IpAddress>) -> Result<(), MulticastError> {
self.with_mut(|i| i.iface.join_multicast_group(addr))
}
/// Leave a multicast group.
pub async fn leave_multicast_group<T>(&self, addr: T) -> Result<bool, MulticastError>
where
T: Into<IpAddress>,
{
let addr = addr.into();
poll_fn(move |cx| self.poll_leave_multicast_group(addr, cx)).await
}
/// Leave a multicast group.
///
/// When the send queue is full, this method will return `Poll::Pending`
/// and register the current task to be notified when the queue has space available.
pub fn poll_leave_multicast_group<T>(&self, addr: T, cx: &mut Context<'_>) -> Poll<Result<bool, MulticastError>>
where
T: Into<IpAddress>,
{
let addr = addr.into();
self.with_mut(|s, i| {
let (_hardware_addr, medium) = to_smoltcp_hardware_address(i.device.hardware_address());
let mut smoldev = DriverAdapter {
cx: Some(cx),
inner: &mut i.device,
medium,
};
match s
.iface
.leave_multicast_group(&mut smoldev, addr, instant_to_smoltcp(Instant::now()))
{
Ok(leave_sent) => Poll::Ready(Ok(leave_sent)),
Err(MulticastError::Exhausted) => Poll::Pending,
Err(other) => Poll::Ready(Err(other)),
}
})
pub fn leave_multicast_group(&self, addr: impl Into<IpAddress>) -> Result<(), MulticastError> {
self.with_mut(|i| i.iface.leave_multicast_group(addr))
}
/// Get whether the network stack has joined the given multicast group.
pub fn has_multicast_group<T: Into<IpAddress>>(&self, addr: T) -> bool {
self.socket.borrow().iface.has_multicast_group(addr)
pub fn has_multicast_group(&self, addr: impl Into<IpAddress>) -> bool {
self.with(|i| i.iface.has_multicast_group(addr))
}
}
impl SocketStack {
impl Inner {
#[allow(clippy::absurd_extreme_comparisons, dead_code)]
pub fn get_local_port(&mut self) -> u16 {
let res = self.next_local_port;
self.next_local_port = if res >= LOCAL_PORT_MAX { LOCAL_PORT_MIN } else { res + 1 };
res
}
}
impl<D: Driver> Inner<D> {
#[cfg(feature = "proto-ipv4")]
pub fn set_config_v4(&mut self, _s: &mut SocketStack, config: ConfigV4) {
pub fn set_config_v4(&mut self, config: ConfigV4) {
// Handle static config.
self.static_v4 = match config.clone() {
ConfigV4::None => None,
@ -717,12 +666,12 @@ impl<D: Driver> Inner<D> {
// Create the socket if it doesn't exist.
if self.dhcp_socket.is_none() {
let socket = smoltcp::socket::dhcpv4::Socket::new();
let handle = _s.sockets.add(socket);
let handle = self.sockets.add(socket);
self.dhcp_socket = Some(handle);
}
// Configure it
let socket = _s.sockets.get_mut::<dhcpv4::Socket>(unwrap!(self.dhcp_socket));
let socket = self.sockets.get_mut::<dhcpv4::Socket>(unwrap!(self.dhcp_socket));
socket.set_ignore_naks(c.ignore_naks);
socket.set_max_lease_duration(c.max_lease_duration.map(crate::time::duration_to_smoltcp));
socket.set_ports(c.server_port, c.client_port);
@ -731,19 +680,20 @@ impl<D: Driver> Inner<D> {
socket.set_outgoing_options(&[]);
#[cfg(feature = "dhcpv4-hostname")]
if let Some(h) = c.hostname {
// safety: we just did set_outgoing_options([]) so we know the socket is no longer holding a reference.
let hostname = unsafe { &mut *self.hostname.get() };
// safety:
// - we just did set_outgoing_options([]) so we know the socket is no longer holding a reference.
// - we know this pointer lives for as long as the stack exists, because `new()` borrows
// the resources for `'d`. Therefore it's OK to pass a reference to this to smoltcp.
let hostname = unsafe { &mut *self.hostname };
// create data
// safety: we know the buffer lives forever, new borrows the StackResources for 'static.
// also we won't modify it until next call to this function.
hostname.data[..h.len()].copy_from_slice(h.as_bytes());
let data: &[u8] = &hostname.data[..h.len()];
let data: &'static [u8] = unsafe { core::mem::transmute(data) };
let data = hostname.data.write([0; MAX_HOSTNAME_LEN]);
data[..h.len()].copy_from_slice(h.as_bytes());
let data: &[u8] = &data[..h.len()];
// set the option.
hostname.option = smoltcp::wire::DhcpOption { data, kind: 12 };
socket.set_outgoing_options(core::slice::from_ref(&hostname.option));
let option = hostname.option.write(smoltcp::wire::DhcpOption { data, kind: 12 });
socket.set_outgoing_options(core::slice::from_ref(option));
}
socket.reset();
@ -751,7 +701,7 @@ impl<D: Driver> Inner<D> {
_ => {
// Remove DHCP socket if any.
if let Some(socket) = self.dhcp_socket {
_s.sockets.remove(socket);
self.sockets.remove(socket);
self.dhcp_socket = None;
}
}
@ -759,14 +709,14 @@ impl<D: Driver> Inner<D> {
}
#[cfg(feature = "proto-ipv6")]
pub fn set_config_v6(&mut self, _s: &mut SocketStack, config: ConfigV6) {
pub fn set_config_v6(&mut self, config: ConfigV6) {
self.static_v6 = match config {
ConfigV6::None => None,
ConfigV6::Static(c) => Some(c),
};
}
fn apply_static_config(&mut self, s: &mut SocketStack) {
fn apply_static_config(&mut self) {
let mut addrs = Vec::new();
#[cfg(feature = "dns")]
let mut dns_servers: Vec<_, 6> = Vec::new();
@ -810,20 +760,20 @@ impl<D: Driver> Inner<D> {
}
// Apply addresses
s.iface.update_ip_addrs(|a| *a = addrs);
self.iface.update_ip_addrs(|a| *a = addrs);
// Apply gateways
#[cfg(feature = "proto-ipv4")]
if let Some(gateway) = gateway_v4 {
unwrap!(s.iface.routes_mut().add_default_ipv4_route(gateway));
unwrap!(self.iface.routes_mut().add_default_ipv4_route(gateway));
} else {
s.iface.routes_mut().remove_default_ipv4_route();
self.iface.routes_mut().remove_default_ipv4_route();
}
#[cfg(feature = "proto-ipv6")]
if let Some(gateway) = gateway_v6 {
unwrap!(s.iface.routes_mut().add_default_ipv6_route(gateway));
unwrap!(self.iface.routes_mut().add_default_ipv6_route(gateway));
} else {
s.iface.routes_mut().remove_default_ipv6_route();
self.iface.routes_mut().remove_default_ipv6_route();
}
// Apply DNS servers
@ -835,18 +785,18 @@ impl<D: Driver> Inner<D> {
} else {
dns_servers.len()
};
s.sockets
self.sockets
.get_mut::<smoltcp::socket::dns::Socket>(self.dns_socket)
.update_servers(&dns_servers[..count]);
}
self.config_waker.wake();
self.state_waker.wake();
}
fn poll(&mut self, cx: &mut Context<'_>, s: &mut SocketStack) {
s.waker.register(cx.waker());
fn poll<D: Driver>(&mut self, cx: &mut Context<'_>, driver: &mut D) {
self.waker.register(cx.waker());
let (_hardware_addr, medium) = to_smoltcp_hardware_address(self.device.hardware_address());
let (_hardware_addr, medium) = to_smoltcp_hardware_address(driver.hardware_address());
#[cfg(any(feature = "medium-ethernet", feature = "medium-ieee802154"))]
{
@ -859,25 +809,26 @@ impl<D: Driver> Inner<D> {
_ => false,
};
if do_set {
s.iface.set_hardware_addr(_hardware_addr);
self.iface.set_hardware_addr(_hardware_addr);
}
}
let timestamp = instant_to_smoltcp(Instant::now());
let mut smoldev = DriverAdapter {
cx: Some(cx),
inner: &mut self.device,
inner: driver,
medium,
};
s.iface.poll(timestamp, &mut smoldev, &mut s.sockets);
self.iface.poll(timestamp, &mut smoldev, &mut self.sockets);
// Update link up
let old_link_up = self.link_up;
self.link_up = self.device.link_state(cx) == LinkState::Up;
self.link_up = driver.link_state(cx) == LinkState::Up;
// Print when changed
if old_link_up != self.link_up {
info!("link_up = {:?}", self.link_up);
self.state_waker.wake();
}
#[allow(unused_mut)]
@ -885,7 +836,7 @@ impl<D: Driver> Inner<D> {
#[cfg(feature = "dhcpv4")]
if let Some(dhcp_handle) = self.dhcp_socket {
let socket = s.sockets.get_mut::<dhcpv4::Socket>(dhcp_handle);
let socket = self.sockets.get_mut::<dhcpv4::Socket>(dhcp_handle);
if self.link_up {
if old_link_up != self.link_up {
@ -914,10 +865,10 @@ impl<D: Driver> Inner<D> {
}
if apply_config {
self.apply_static_config(s);
self.apply_static_config();
}
if let Some(poll_at) = s.iface.poll_at(timestamp, &mut s.sockets) {
if let Some(poll_at) = self.iface.poll_at(timestamp, &mut self.sockets) {
let t = pin!(Timer::at(instant_from_smoltcp(poll_at)));
if t.poll(cx).is_ready() {
cx.waker().wake_by_ref();
@ -925,3 +876,17 @@ impl<D: Driver> Inner<D> {
}
}
}
impl<'d, D: Driver> Runner<'d, D> {
/// Run the network stack.
///
/// You must call this in a background task, to process network events.
pub async fn run(&mut self) -> ! {
poll_fn(|cx| {
self.stack.with_mut(|i| i.poll(cx, &mut self.driver));
Poll::<()>::Pending
})
.await;
unreachable!()
}
}

View File

@ -1,6 +1,5 @@
//! Raw sockets.
use core::cell::RefCell;
use core::future::poll_fn;
use core::mem;
use core::task::{Context, Poll};
@ -11,7 +10,7 @@ use smoltcp::socket::raw;
pub use smoltcp::socket::raw::PacketMetadata;
use smoltcp::wire::{IpProtocol, IpVersion};
use crate::{SocketStack, Stack};
use crate::Stack;
/// Error returned by [`RawSocket::recv`] and [`RawSocket::send`].
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
@ -23,14 +22,14 @@ pub enum RecvError {
/// An Raw socket.
pub struct RawSocket<'a> {
stack: &'a RefCell<SocketStack>,
stack: Stack<'a>,
handle: SocketHandle,
}
impl<'a> RawSocket<'a> {
/// Create a new Raw socket using the provided stack and buffers.
pub fn new<D: Driver>(
stack: &'a Stack<D>,
stack: Stack<'a>,
ip_version: IpVersion,
ip_protocol: IpProtocol,
rx_meta: &'a mut [PacketMetadata],
@ -38,31 +37,29 @@ impl<'a> RawSocket<'a> {
tx_meta: &'a mut [PacketMetadata],
tx_buffer: &'a mut [u8],
) -> Self {
let s = &mut *stack.socket.borrow_mut();
let handle = stack.with_mut(|i| {
let rx_meta: &'static mut [PacketMetadata] = unsafe { mem::transmute(rx_meta) };
let rx_buffer: &'static mut [u8] = unsafe { mem::transmute(rx_buffer) };
let tx_meta: &'static mut [PacketMetadata] = unsafe { mem::transmute(tx_meta) };
let tx_buffer: &'static mut [u8] = unsafe { mem::transmute(tx_buffer) };
i.sockets.add(raw::Socket::new(
ip_version,
ip_protocol,
raw::PacketBuffer::new(rx_meta, rx_buffer),
raw::PacketBuffer::new(tx_meta, tx_buffer),
))
});
let rx_meta: &'static mut [PacketMetadata] = unsafe { mem::transmute(rx_meta) };
let rx_buffer: &'static mut [u8] = unsafe { mem::transmute(rx_buffer) };
let tx_meta: &'static mut [PacketMetadata] = unsafe { mem::transmute(tx_meta) };
let tx_buffer: &'static mut [u8] = unsafe { mem::transmute(tx_buffer) };
let handle = s.sockets.add(raw::Socket::new(
ip_version,
ip_protocol,
raw::PacketBuffer::new(rx_meta, rx_buffer),
raw::PacketBuffer::new(tx_meta, tx_buffer),
));
Self {
stack: &stack.socket,
handle,
}
Self { stack, handle }
}
fn with_mut<R>(&self, f: impl FnOnce(&mut raw::Socket, &mut Interface) -> R) -> R {
let s = &mut *self.stack.borrow_mut();
let socket = s.sockets.get_mut::<raw::Socket>(self.handle);
let res = f(socket, &mut s.iface);
s.waker.wake();
res
self.stack.with_mut(|i| {
let socket = i.sockets.get_mut::<raw::Socket>(self.handle);
let res = f(socket, &mut i.iface);
i.waker.wake();
res
})
}
/// Receive a datagram.
@ -115,6 +112,10 @@ impl<'a> RawSocket<'a> {
impl Drop for RawSocket<'_> {
fn drop(&mut self) {
self.stack.borrow_mut().sockets.remove(self.handle);
self.stack.with_mut(|i| i.sockets.remove(self.handle));
}
}
fn _assert_covariant<'a, 'b: 'a>(x: RawSocket<'b>) -> RawSocket<'a> {
x
}

View File

@ -8,12 +8,10 @@
//! Incoming connections when no socket is listening are rejected. To accept many incoming
//! connections, create many sockets and put them all into listening mode.
use core::cell::RefCell;
use core::future::poll_fn;
use core::mem;
use core::task::Poll;
use embassy_net_driver::Driver;
use embassy_time::Duration;
use smoltcp::iface::{Interface, SocketHandle};
use smoltcp::socket::tcp;
@ -21,7 +19,7 @@ pub use smoltcp::socket::tcp::State;
use smoltcp::wire::{IpEndpoint, IpListenEndpoint};
use crate::time::duration_to_smoltcp;
use crate::{SocketStack, Stack};
use crate::Stack;
/// Error returned by TcpSocket read/write functions.
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
@ -79,6 +77,15 @@ impl<'a> TcpReader<'a> {
///
/// Returns how many bytes were read, or an error. If no data is available, it waits
/// until there is at least one byte available.
///
/// # Note
/// A return value of Ok(0) means that we have read all data and the remote
/// side has closed our receive half of the socket. The remote can no longer
/// send bytes.
///
/// The send half of the socket is still open. If you want to reconnect using
/// the socket you split this reader off the send half needs to be closed using
/// [`abort()`](TcpSocket::abort).
pub async fn read(&mut self, buf: &mut [u8]) -> Result<usize, Error> {
self.io.read(buf).await
}
@ -148,20 +155,18 @@ impl<'a> TcpWriter<'a> {
impl<'a> TcpSocket<'a> {
/// Create a new TCP socket on the given stack, with the given buffers.
pub fn new<D: Driver>(stack: &'a Stack<D>, rx_buffer: &'a mut [u8], tx_buffer: &'a mut [u8]) -> Self {
let s = &mut *stack.socket.borrow_mut();
let rx_buffer: &'static mut [u8] = unsafe { mem::transmute(rx_buffer) };
let tx_buffer: &'static mut [u8] = unsafe { mem::transmute(tx_buffer) };
let handle = s.sockets.add(tcp::Socket::new(
tcp::SocketBuffer::new(rx_buffer),
tcp::SocketBuffer::new(tx_buffer),
));
pub fn new(stack: Stack<'a>, rx_buffer: &'a mut [u8], tx_buffer: &'a mut [u8]) -> Self {
let handle = stack.with_mut(|i| {
let rx_buffer: &'static mut [u8] = unsafe { mem::transmute(rx_buffer) };
let tx_buffer: &'static mut [u8] = unsafe { mem::transmute(tx_buffer) };
i.sockets.add(tcp::Socket::new(
tcp::SocketBuffer::new(rx_buffer),
tcp::SocketBuffer::new(tx_buffer),
))
});
Self {
io: TcpIo {
stack: &stack.socket,
handle,
},
io: TcpIo { stack: stack, handle },
}
}
@ -219,7 +224,7 @@ impl<'a> TcpSocket<'a> {
where
T: Into<IpEndpoint>,
{
let local_port = self.io.stack.borrow_mut().get_local_port();
let local_port = self.io.stack.with_mut(|i| i.get_local_port());
match {
self.io
@ -273,6 +278,9 @@ impl<'a> TcpSocket<'a> {
///
/// Returns how many bytes were read, or an error. If no data is available, it waits
/// until there is at least one byte available.
///
/// A return value of Ok(0) means that the socket was closed and is longer
/// able to receive any data.
pub async fn read(&mut self, buf: &mut [u8]) -> Result<usize, Error> {
self.io.read(buf).await
}
@ -297,6 +305,10 @@ impl<'a> TcpSocket<'a> {
///
/// If the timeout is set, the socket will be closed if no data is received for the
/// specified duration.
///
/// # Note:
/// Set a keep alive interval ([`set_keep_alive`] to prevent timeouts when
/// the remote could still respond.
pub fn set_timeout(&mut self, duration: Option<Duration>) {
self.io
.with_mut(|s, _| s.set_timeout(duration.map(duration_to_smoltcp)))
@ -308,6 +320,9 @@ impl<'a> TcpSocket<'a> {
/// the specified duration of inactivity.
///
/// If not set, the socket will not send keep-alive packets.
///
/// By setting a [`timeout`](Self::timeout) larger then the keep alive you
/// can detect a remote endpoint that no longer answers.
pub fn set_keep_alive(&mut self, interval: Option<Duration>) {
self.io
.with_mut(|s, _| s.set_keep_alive(interval.map(duration_to_smoltcp)))
@ -382,31 +397,43 @@ impl<'a> TcpSocket<'a> {
impl<'a> Drop for TcpSocket<'a> {
fn drop(&mut self) {
self.io.stack.borrow_mut().sockets.remove(self.io.handle);
self.io.stack.with_mut(|i| i.sockets.remove(self.io.handle));
}
}
fn _assert_covariant<'a, 'b: 'a>(x: TcpSocket<'b>) -> TcpSocket<'a> {
x
}
fn _assert_covariant_reader<'a, 'b: 'a>(x: TcpReader<'b>) -> TcpReader<'a> {
x
}
fn _assert_covariant_writer<'a, 'b: 'a>(x: TcpWriter<'b>) -> TcpWriter<'a> {
x
}
// =======================
#[derive(Copy, Clone)]
struct TcpIo<'a> {
stack: &'a RefCell<SocketStack>,
stack: Stack<'a>,
handle: SocketHandle,
}
impl<'d> TcpIo<'d> {
fn with<R>(&self, f: impl FnOnce(&tcp::Socket, &Interface) -> R) -> R {
let s = &*self.stack.borrow();
let socket = s.sockets.get::<tcp::Socket>(self.handle);
f(socket, &s.iface)
self.stack.with(|i| {
let socket = i.sockets.get::<tcp::Socket>(self.handle);
f(socket, &i.iface)
})
}
fn with_mut<R>(&mut self, f: impl FnOnce(&mut tcp::Socket, &mut Interface) -> R) -> R {
let s = &mut *self.stack.borrow_mut();
let socket = s.sockets.get_mut::<tcp::Socket>(self.handle);
let res = f(socket, &mut s.iface);
s.waker.wake();
res
self.stack.with_mut(|i| {
let socket = i.sockets.get_mut::<tcp::Socket>(self.handle);
let res = f(socket, &mut i.iface);
i.waker.wake();
res
})
}
async fn read(&mut self, buf: &mut [u8]) -> Result<usize, Error> {
@ -657,15 +684,15 @@ pub mod client {
/// TCP client connection pool compatible with `embedded-nal-async` traits.
///
/// The pool is capable of managing up to N concurrent connections with tx and rx buffers according to TX_SZ and RX_SZ.
pub struct TcpClient<'d, D: Driver, const N: usize, const TX_SZ: usize = 1024, const RX_SZ: usize = 1024> {
stack: &'d Stack<D>,
pub struct TcpClient<'d, const N: usize, const TX_SZ: usize = 1024, const RX_SZ: usize = 1024> {
stack: Stack<'d>,
state: &'d TcpClientState<N, TX_SZ, RX_SZ>,
socket_timeout: Option<Duration>,
}
impl<'d, D: Driver, const N: usize, const TX_SZ: usize, const RX_SZ: usize> TcpClient<'d, D, N, TX_SZ, RX_SZ> {
impl<'d, const N: usize, const TX_SZ: usize, const RX_SZ: usize> TcpClient<'d, N, TX_SZ, RX_SZ> {
/// Create a new `TcpClient`.
pub fn new(stack: &'d Stack<D>, state: &'d TcpClientState<N, TX_SZ, RX_SZ>) -> Self {
pub fn new(stack: Stack<'d>, state: &'d TcpClientState<N, TX_SZ, RX_SZ>) -> Self {
Self {
stack,
state,
@ -682,8 +709,8 @@ pub mod client {
}
}
impl<'d, D: Driver, const N: usize, const TX_SZ: usize, const RX_SZ: usize> embedded_nal_async::TcpConnect
for TcpClient<'d, D, N, TX_SZ, RX_SZ>
impl<'d, const N: usize, const TX_SZ: usize, const RX_SZ: usize> embedded_nal_async::TcpConnect
for TcpClient<'d, N, TX_SZ, RX_SZ>
{
type Error = Error;
type Connection<'m> = TcpConnection<'m, N, TX_SZ, RX_SZ> where Self: 'm;
@ -703,7 +730,7 @@ pub mod client {
IpAddr::V6(_) => panic!("ipv6 support not enabled"),
};
let remote_endpoint = (addr, remote.port());
let mut socket = TcpConnection::new(&self.stack, self.state)?;
let mut socket = TcpConnection::new(self.stack, self.state)?;
socket.socket.set_timeout(self.socket_timeout.clone());
socket
.socket
@ -722,7 +749,7 @@ pub mod client {
}
impl<'d, const N: usize, const TX_SZ: usize, const RX_SZ: usize> TcpConnection<'d, N, TX_SZ, RX_SZ> {
fn new<D: Driver>(stack: &'d Stack<D>, state: &'d TcpClientState<N, TX_SZ, RX_SZ>) -> Result<Self, Error> {
fn new(stack: Stack<'d>, state: &'d TcpClientState<N, TX_SZ, RX_SZ>) -> Result<Self, Error> {
let mut bufs = state.pool.alloc().ok_or(Error::ConnectionReset)?;
Ok(Self {
socket: unsafe { TcpSocket::new(stack, &mut bufs.as_mut().1, &mut bufs.as_mut().0) },

View File

@ -1,17 +1,15 @@
//! UDP sockets.
use core::cell::RefCell;
use core::future::poll_fn;
use core::mem;
use core::task::{Context, Poll};
use embassy_net_driver::Driver;
use smoltcp::iface::{Interface, SocketHandle};
use smoltcp::socket::udp;
pub use smoltcp::socket::udp::{PacketMetadata, UdpMetadata};
use smoltcp::wire::IpListenEndpoint;
use crate::{SocketStack, Stack};
use crate::Stack;
/// Error returned by [`UdpSocket::bind`].
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
@ -43,34 +41,31 @@ pub enum RecvError {
/// An UDP socket.
pub struct UdpSocket<'a> {
stack: &'a RefCell<SocketStack>,
stack: Stack<'a>,
handle: SocketHandle,
}
impl<'a> UdpSocket<'a> {
/// Create a new UDP socket using the provided stack and buffers.
pub fn new<D: Driver>(
stack: &'a Stack<D>,
pub fn new(
stack: Stack<'a>,
rx_meta: &'a mut [PacketMetadata],
rx_buffer: &'a mut [u8],
tx_meta: &'a mut [PacketMetadata],
tx_buffer: &'a mut [u8],
) -> Self {
let s = &mut *stack.socket.borrow_mut();
let handle = stack.with_mut(|i| {
let rx_meta: &'static mut [PacketMetadata] = unsafe { mem::transmute(rx_meta) };
let rx_buffer: &'static mut [u8] = unsafe { mem::transmute(rx_buffer) };
let tx_meta: &'static mut [PacketMetadata] = unsafe { mem::transmute(tx_meta) };
let tx_buffer: &'static mut [u8] = unsafe { mem::transmute(tx_buffer) };
i.sockets.add(udp::Socket::new(
udp::PacketBuffer::new(rx_meta, rx_buffer),
udp::PacketBuffer::new(tx_meta, tx_buffer),
))
});
let rx_meta: &'static mut [PacketMetadata] = unsafe { mem::transmute(rx_meta) };
let rx_buffer: &'static mut [u8] = unsafe { mem::transmute(rx_buffer) };
let tx_meta: &'static mut [PacketMetadata] = unsafe { mem::transmute(tx_meta) };
let tx_buffer: &'static mut [u8] = unsafe { mem::transmute(tx_buffer) };
let handle = s.sockets.add(udp::Socket::new(
udp::PacketBuffer::new(rx_meta, rx_buffer),
udp::PacketBuffer::new(tx_meta, tx_buffer),
));
Self {
stack: &stack.socket,
handle,
}
Self { stack, handle }
}
/// Bind the socket to a local endpoint.
@ -82,7 +77,7 @@ impl<'a> UdpSocket<'a> {
if endpoint.port == 0 {
// If user didn't specify port allocate a dynamic port.
endpoint.port = self.stack.borrow_mut().get_local_port();
endpoint.port = self.stack.with_mut(|i| i.get_local_port());
}
match self.with_mut(|s, _| s.bind(endpoint)) {
@ -93,17 +88,19 @@ impl<'a> UdpSocket<'a> {
}
fn with<R>(&self, f: impl FnOnce(&udp::Socket, &Interface) -> R) -> R {
let s = &*self.stack.borrow();
let socket = s.sockets.get::<udp::Socket>(self.handle);
f(socket, &s.iface)
self.stack.with(|i| {
let socket = i.sockets.get::<udp::Socket>(self.handle);
f(socket, &i.iface)
})
}
fn with_mut<R>(&self, f: impl FnOnce(&mut udp::Socket, &mut Interface) -> R) -> R {
let s = &mut *self.stack.borrow_mut();
let socket = s.sockets.get_mut::<udp::Socket>(self.handle);
let res = f(socket, &mut s.iface);
s.waker.wake();
res
self.stack.with_mut(|i| {
let socket = i.sockets.get_mut::<udp::Socket>(self.handle);
let res = f(socket, &mut i.iface);
i.waker.wake();
res
})
}
/// Receive a datagram.
@ -138,6 +135,35 @@ impl<'a> UdpSocket<'a> {
})
}
/// Receive a datagram with a zero-copy function.
///
/// When no datagram is available, this method will return `Poll::Pending` and
/// register the current task to be notified when a datagram is received.
///
/// When a datagram is received, this method will call the provided function
/// with the number of bytes received and the remote endpoint and return
/// `Poll::Ready` with the function's returned value.
pub async fn recv_from_with<F, R>(&mut self, f: F) -> R
where
F: FnOnce(&[u8], UdpMetadata) -> R,
{
let mut f = Some(f);
poll_fn(move |cx| {
self.with_mut(|s, _| {
match s.recv() {
Ok((buffer, endpoint)) => Poll::Ready(unwrap!(f.take())(buffer, endpoint)),
Err(udp::RecvError::Truncated) => unreachable!(),
Err(udp::RecvError::Exhausted) => {
// socket buffer is empty wait until at least one byte has arrived
s.register_recv_waker(cx.waker());
Poll::Pending
}
}
})
})
.await
}
/// Send a datagram to the specified remote endpoint.
///
/// This method will wait until the datagram has been sent.
@ -181,6 +207,40 @@ impl<'a> UdpSocket<'a> {
})
}
/// Send a datagram to the specified remote endpoint with a zero-copy function.
///
/// This method will wait until the buffer can fit the requested size before
/// calling the function to fill its contents.
///
/// When the remote endpoint is not reachable, this method will return `Err(SendError::NoRoute)`
pub async fn send_to_with<T, F, R>(&mut self, size: usize, remote_endpoint: T, f: F) -> Result<R, SendError>
where
T: Into<UdpMetadata> + Copy,
F: FnOnce(&mut [u8]) -> R,
{
let mut f = Some(f);
poll_fn(move |cx| {
self.with_mut(|s, _| {
match s.send(size, remote_endpoint) {
Ok(buffer) => Poll::Ready(Ok(unwrap!(f.take())(buffer))),
Err(udp::SendError::BufferFull) => {
s.register_send_waker(cx.waker());
Poll::Pending
}
Err(udp::SendError::Unaddressable) => {
// If no sender/outgoing port is specified, there is not really "no route"
if s.endpoint().port == 0 {
Poll::Ready(Err(SendError::SocketNotBound))
} else {
Poll::Ready(Err(SendError::NoRoute))
}
}
}
})
})
.await
}
/// Returns the local endpoint of the socket.
pub fn endpoint(&self) -> IpListenEndpoint {
self.with(|s, _| s.endpoint())
@ -235,6 +295,10 @@ impl<'a> UdpSocket<'a> {
impl Drop for UdpSocket<'_> {
fn drop(&mut self) {
self.stack.borrow_mut().sockets.remove(self.handle);
self.stack.with_mut(|i| i.sockets.remove(self.handle));
}
}
fn _assert_covariant<'a, 'b: 'a>(x: UdpSocket<'b>) -> UdpSocket<'a> {
x
}

View File

@ -358,6 +358,11 @@ impl<'d, U: UarteInstance, T: TimerInstance> BufferedUarte<'d, U, T> {
self.tx.write(buf).await
}
/// Try writing a buffer without waiting, returning how many bytes were written.
pub fn try_write(&mut self, buf: &[u8]) -> Result<usize, Error> {
self.tx.try_write(buf)
}
/// Flush this output stream, ensuring that all intermediately buffered contents reach their destination.
pub async fn flush(&mut self) -> Result<(), Error> {
self.tx.flush().await
@ -482,6 +487,29 @@ impl<'d, U: UarteInstance> BufferedUarteTx<'d, U> {
.await
}
/// Try writing a buffer without waiting, returning how many bytes were written.
pub fn try_write(&mut self, buf: &[u8]) -> Result<usize, Error> {
//trace!("poll_write: {:?}", buf.len());
let s = U::buffered_state();
let mut tx = unsafe { s.tx_buf.writer() };
let tx_buf = tx.push_slice();
if tx_buf.is_empty() {
return Ok(0);
}
let n = min(tx_buf.len(), buf.len());
tx_buf[..n].copy_from_slice(&buf[..n]);
tx.push_done(n);
//trace!("poll_write: queued {:?}", n);
compiler_fence(Ordering::SeqCst);
U::Interrupt::pend();
Ok(n)
}
/// Flush this output stream, ensuring that all intermediately buffered contents reach their destination.
pub async fn flush(&mut self) -> Result<(), Error> {
poll_fn(move |cx| {

View File

@ -512,12 +512,18 @@ pub(crate) unsafe fn init(config: ClockConfig) {
w.set_int(config.ref_clk.div);
});
// Configure tick generation on the 2040. On the 2350 the timers are driven from the sysclk.
// Configure tick generation on the 2040.
#[cfg(feature = "rp2040")]
pac::WATCHDOG.tick().write(|w| {
w.set_cycles((clk_ref_freq / 1_000_000) as u16);
w.set_enable(true);
});
// Configure tick generator on the 2350
#[cfg(feature = "_rp235x")]
{
pac::TICKS.timer0_cycles().write(|w| w.0 = clk_ref_freq / 1_000_000);
pac::TICKS.timer0_ctrl().write(|w| w.set_enable(true));
}
let (sys_src, sys_aux, clk_sys_freq) = {
use {ClkSysCtrlAuxsrc as Aux, ClkSysCtrlSrc as Src};
@ -841,6 +847,10 @@ impl<'d, T: GpinPin> Gpin<'d, T> {
into_ref!(gpin);
gpin.gpio().ctrl().write(|w| w.set_funcsel(0x08));
#[cfg(feature = "_rp235x")]
gpin.pad_ctrl().write(|w| {
w.set_iso(false);
});
Gpin {
gpin: gpin.map_into(),
@ -855,6 +865,7 @@ impl<'d, T: GpinPin> Gpin<'d, T> {
impl<'d, T: GpinPin> Drop for Gpin<'d, T> {
fn drop(&mut self) {
self.gpin.pad_ctrl().write(|_| {});
self.gpin
.gpio()
.ctrl()
@ -915,11 +926,15 @@ pub struct Gpout<'d, T: GpoutPin> {
}
impl<'d, T: GpoutPin> Gpout<'d, T> {
/// Create new general purpose cloud output.
/// Create new general purpose clock output.
pub fn new(gpout: impl Peripheral<P = T> + 'd) -> Self {
into_ref!(gpout);
gpout.gpio().ctrl().write(|w| w.set_funcsel(0x08));
#[cfg(feature = "_rp235x")]
gpout.pad_ctrl().write(|w| {
w.set_iso(false);
});
Self { gpout }
}
@ -999,6 +1014,7 @@ impl<'d, T: GpoutPin> Gpout<'d, T> {
impl<'d, T: GpoutPin> Drop for Gpout<'d, T> {
fn drop(&mut self) {
self.disable();
self.gpout.pad_ctrl().write(|_| {});
self.gpout
.gpio()
.ctrl()

View File

@ -17,9 +17,13 @@ use crate::peripherals::FLASH;
/// Flash base address.
pub const FLASH_BASE: *const u32 = 0x10000000 as _;
/// Address for xip setup function set up by the 235x bootrom.
#[cfg(feature = "_rp235x")]
pub const BOOTROM_BASE: *const u32 = 0x400e0000 as _;
/// If running from RAM, we might have no boot2. Use bootrom `flash_enter_cmd_xip` instead.
// TODO: when run-from-ram is set, completely skip the "pause cores and jumpp to RAM" dance.
pub const USE_BOOT2: bool = !cfg!(feature = "run-from-ram");
pub const USE_BOOT2: bool = !cfg!(feature = "run-from-ram") | cfg!(feature = "_rp235x");
// **NOTE**:
//
@ -97,7 +101,10 @@ impl<'a, 'd, T: Instance, const FLASH_SIZE: usize> Drop for BackgroundRead<'a, '
// Errata RP2040-E8: Perform an uncached read to make sure there's not a transfer in
// flight that might effect an address written to start a new transfer. This stalls
// until after any transfer is complete, so the address will not change anymore.
#[cfg(feature = "rp2040")]
const XIP_NOCACHE_NOALLOC_BASE: *const u32 = 0x13000000 as *const _;
#[cfg(feature = "_rp235x")]
const XIP_NOCACHE_NOALLOC_BASE: *const u32 = 0x14000000 as *const _;
unsafe {
core::ptr::read_volatile(XIP_NOCACHE_NOALLOC_BASE);
}
@ -225,12 +232,14 @@ impl<'d, T: Instance, M: Mode, const FLASH_SIZE: usize> Flash<'d, T, M, FLASH_SI
}
/// Read SPI flash unique ID
#[cfg(feature = "rp2040")]
pub fn blocking_unique_id(&mut self, uid: &mut [u8]) -> Result<(), Error> {
unsafe { in_ram(|| ram_helpers::flash_unique_id(uid))? };
Ok(())
}
/// Read SPI flash JEDEC ID
#[cfg(feature = "rp2040")]
pub fn blocking_jedec_id(&mut self) -> Result<u32, Error> {
let mut jedec = None;
unsafe {
@ -301,7 +310,10 @@ impl<'d, T: Instance, const FLASH_SIZE: usize> Flash<'d, T, Async, FLASH_SIZE> {
// Use the XIP AUX bus port, rather than the FIFO register access (e.x.
// pac::XIP_CTRL.stream_fifo().as_ptr()) to avoid DMA stalling on
// general XIP access.
#[cfg(feature = "rp2040")]
const XIP_AUX_BASE: *const u32 = 0x50400000 as *const _;
#[cfg(feature = "_rp235x")]
const XIP_AUX_BASE: *const u32 = 0x50500000 as *const _;
let transfer = unsafe {
crate::dma::read(
self.dma.as_mut().unwrap(),
@ -512,7 +524,10 @@ mod ram_helpers {
pub unsafe fn flash_range_erase(addr: u32, len: u32) {
let mut boot2 = [0u32; 256 / 4];
let ptrs = if USE_BOOT2 {
#[cfg(feature = "rp2040")]
rom_data::memcpy44(&mut boot2 as *mut _, FLASH_BASE, 256);
#[cfg(feature = "_rp235x")]
core::ptr::copy_nonoverlapping(BOOTROM_BASE as *const u8, boot2.as_mut_ptr() as *mut u8, 256);
flash_function_pointers_with_boot2(true, false, &boot2)
} else {
flash_function_pointers(true, false)
@ -542,7 +557,10 @@ mod ram_helpers {
pub unsafe fn flash_range_erase_and_program(addr: u32, data: &[u8]) {
let mut boot2 = [0u32; 256 / 4];
let ptrs = if USE_BOOT2 {
#[cfg(feature = "rp2040")]
rom_data::memcpy44(&mut boot2 as *mut _, FLASH_BASE, 256);
#[cfg(feature = "_rp235x")]
core::ptr::copy_nonoverlapping(BOOTROM_BASE as *const u8, (boot2).as_mut_ptr() as *mut u8, 256);
flash_function_pointers_with_boot2(true, true, &boot2)
} else {
flash_function_pointers(true, true)
@ -577,7 +595,10 @@ mod ram_helpers {
pub unsafe fn flash_range_program(addr: u32, data: &[u8]) {
let mut boot2 = [0u32; 256 / 4];
let ptrs = if USE_BOOT2 {
#[cfg(feature = "rp2040")]
rom_data::memcpy44(&mut boot2 as *mut _, FLASH_BASE, 256);
#[cfg(feature = "_rp235x")]
core::ptr::copy_nonoverlapping(BOOTROM_BASE as *const u8, boot2.as_mut_ptr() as *mut u8, 256);
flash_function_pointers_with_boot2(false, true, &boot2)
} else {
flash_function_pointers(false, true)
@ -606,15 +627,6 @@ mod ram_helpers {
#[link_section = ".data.ram_func"]
#[cfg(feature = "rp2040")]
unsafe fn write_flash_inner(addr: u32, len: u32, data: Option<&[u8]>, ptrs: *const FlashFunctionPointers) {
/*
Should be equivalent to:
rom_data::connect_internal_flash();
rom_data::flash_exit_xip();
rom_data::flash_range_erase(addr, len, 1 << 31, 0); // if selected
rom_data::flash_range_program(addr, data as *const _, len); // if selected
rom_data::flash_flush_cache();
rom_data::flash_enter_cmd_xip();
*/
#[cfg(target_arch = "arm")]
core::arch::asm!(
"mov r8, r0",
@ -667,11 +679,30 @@ mod ram_helpers {
);
}
/// # Safety
///
/// Nothing must access flash while this is running.
/// Usually this means:
/// - interrupts must be disabled
/// - 2nd core must be running code from RAM or ROM with interrupts disabled
/// - DMA must not access flash memory
/// Length of data must be a multiple of 4096
/// addr must be aligned to 4096
#[inline(never)]
#[link_section = ".data.ram_func"]
#[cfg(feature = "_rp235x")]
unsafe fn write_flash_inner(_addr: u32, _len: u32, _data: Option<&[u8]>, _ptrs: *const FlashFunctionPointers) {
todo!();
unsafe fn write_flash_inner(addr: u32, len: u32, data: Option<&[u8]>, ptrs: *const FlashFunctionPointers) {
let data = data.map(|d| d.as_ptr()).unwrap_or(core::ptr::null());
((*ptrs).connect_internal_flash)();
((*ptrs).flash_exit_xip)();
if (*ptrs).flash_range_erase.is_some() {
((*ptrs).flash_range_erase.unwrap())(addr, len as usize, 1 << 31, 0);
}
if (*ptrs).flash_range_program.is_some() {
((*ptrs).flash_range_program.unwrap())(addr, data as *const _, len as usize);
}
((*ptrs).flash_flush_cache)();
((*ptrs).flash_enter_cmd_xip)();
}
#[repr(C)]
@ -707,6 +738,7 @@ mod ram_helpers {
/// - DMA must not access flash memory
///
/// Credit: taken from `rp2040-flash` (also licensed Apache+MIT)
#[cfg(feature = "rp2040")]
pub unsafe fn flash_unique_id(out: &mut [u8]) {
let mut boot2 = [0u32; 256 / 4];
let ptrs = if USE_BOOT2 {
@ -715,6 +747,7 @@ mod ram_helpers {
} else {
flash_function_pointers(false, false)
};
// 4B - read unique ID
let cmd = [0x4B];
read_flash(&cmd[..], 4, out, &ptrs as *const FlashFunctionPointers);
@ -735,6 +768,7 @@ mod ram_helpers {
/// - DMA must not access flash memory
///
/// Credit: taken from `rp2040-flash` (also licensed Apache+MIT)
#[cfg(feature = "rp2040")]
pub unsafe fn flash_jedec_id() -> u32 {
let mut boot2 = [0u32; 256 / 4];
let ptrs = if USE_BOOT2 {
@ -743,6 +777,7 @@ mod ram_helpers {
} else {
flash_function_pointers(false, false)
};
let mut id = [0u8; 4];
// 9F - read JEDEC ID
let cmd = [0x9F];
@ -750,6 +785,7 @@ mod ram_helpers {
u32::from_be_bytes(id)
}
#[cfg(feature = "rp2040")]
unsafe fn read_flash(cmd_addr: &[u8], dummy_len: u32, out: &mut [u8], ptrs: *const FlashFunctionPointers) {
read_flash_inner(
FlashCommand {
@ -890,13 +926,6 @@ mod ram_helpers {
clobber_abi("C"),
);
}
#[inline(never)]
#[link_section = ".data.ram_func"]
#[cfg(feature = "_rp235x")]
unsafe fn read_flash_inner(_cmd: FlashCommand, _ptrs: *const FlashFunctionPointers) {
todo!();
}
}
/// Make sure to uphold the contract points with rp2040-flash.

View File

@ -16,9 +16,9 @@ use crate::{interrupt, pac, peripherals, Peripheral, RegExt};
const NEW_AW: AtomicWaker = AtomicWaker::new();
#[cfg(any(feature = "rp2040", feature = "rp235xa"))]
const BANK0_PIN_COUNT: usize = 30;
pub(crate) const BANK0_PIN_COUNT: usize = 30;
#[cfg(feature = "rp235xb")]
const BANK0_PIN_COUNT: usize = 48;
pub(crate) const BANK0_PIN_COUNT: usize = 48;
static BANK0_WAKERS: [AtomicWaker; BANK0_PIN_COUNT] = [NEW_AW; BANK0_PIN_COUNT];
#[cfg(feature = "qspi-as-gpio")]
@ -603,7 +603,7 @@ impl<'d> Flex<'d> {
#[inline]
fn bit(&self) -> u32 {
1 << self.pin.pin()
1 << (self.pin.pin() % 32)
}
/// Set the pin's pull.
@ -846,12 +846,12 @@ pub(crate) trait SealedPin: Sized {
#[inline]
fn _pin(&self) -> u8 {
self.pin_bank() & 0x1f
self.pin_bank() & 0x7f
}
#[inline]
fn _bank(&self) -> Bank {
match self.pin_bank() >> 5 {
match self.pin_bank() >> 7 {
#[cfg(feature = "qspi-as-gpio")]
1 => Bank::Qspi,
_ => Bank::Bank0,
@ -880,15 +880,27 @@ pub(crate) trait SealedPin: Sized {
}
fn sio_out(&self) -> pac::sio::Gpio {
SIO.gpio_out(self._bank() as _)
if cfg!(feature = "rp2040") {
SIO.gpio_out(self._bank() as _)
} else {
SIO.gpio_out((self._pin() / 32) as _)
}
}
fn sio_oe(&self) -> pac::sio::Gpio {
SIO.gpio_oe(self._bank() as _)
if cfg!(feature = "rp2040") {
SIO.gpio_oe(self._bank() as _)
} else {
SIO.gpio_oe((self._pin() / 32) as _)
}
}
fn sio_in(&self) -> Reg<u32, RW> {
SIO.gpio_in(self._bank() as _)
if cfg!(feature = "rp2040") {
SIO.gpio_in(self._bank() as _)
} else {
SIO.gpio_in((self._pin() / 32) as _)
}
}
fn int_proc(&self) -> pac::io::Int {
@ -953,7 +965,7 @@ macro_rules! impl_pin {
impl SealedPin for peripherals::$name {
#[inline]
fn pin_bank(&self) -> u8 {
($bank as u8) * 32 + $pin_num
($bank as u8) * 128 + $pin_num
}
}

View File

@ -15,6 +15,7 @@ pub use rp_binary_info as binary_info;
#[cfg(feature = "critical-section-impl")]
mod critical_section_impl;
#[cfg(feature = "rp2040")]
mod intrinsics;
pub mod adc;
@ -31,6 +32,8 @@ pub mod gpio;
pub mod i2c;
pub mod i2c_slave;
pub mod multicore;
#[cfg(feature = "_rp235x")]
pub mod otp;
pub mod pwm;
mod reset;
pub mod rom_data;
@ -39,6 +42,8 @@ pub mod rtc;
pub mod spi;
#[cfg(feature = "time-driver")]
pub mod time_driver;
#[cfg(feature = "_rp235x")]
pub mod trng;
pub mod uart;
pub mod usb;
pub mod watchdog;
@ -399,9 +404,11 @@ embassy_hal_internal::peripherals! {
WATCHDOG,
BOOTSEL,
TRNG
}
#[cfg(not(feature = "boot2-none"))]
#[cfg(all(not(feature = "boot2-none"), feature = "rp2040"))]
macro_rules! select_bootloader {
( $( $feature:literal => $loader:ident, )+ default => $default:ident ) => {
$(
@ -418,7 +425,7 @@ macro_rules! select_bootloader {
}
}
#[cfg(not(feature = "boot2-none"))]
#[cfg(all(not(feature = "boot2-none"), feature = "rp2040"))]
select_bootloader! {
"boot2-at25sf128a" => BOOT_LOADER_AT25SF128A,
"boot2-gd25q64cs" => BOOT_LOADER_GD25Q64CS,

108
embassy-rp/src/otp.rs Normal file
View File

@ -0,0 +1,108 @@
//! Interface to the RP2350's One Time Programmable Memory
// Credit: taken from `rp-hal` (also licensed Apache+MIT)
// https://github.com/rp-rs/rp-hal/blob/main/rp235x-hal/src/rom_data.rs
/// The ways in which we can fail to read OTP
#[derive(Debug, Clone)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum Error {
/// The user passed an invalid index to a function.
InvalidIndex,
/// The hardware refused to let us read this word, probably due to
/// read lock set earlier in the boot process.
InvalidPermissions,
}
/// OTP read address, using automatic Error Correction.
///
/// A 32-bit read returns the ECC-corrected data for two neighbouring rows, or
/// all-ones on permission failure. Only the first 8 KiB is populated.
pub const OTP_DATA_BASE: *const u32 = 0x4013_0000 as *const u32;
/// OTP read address, without using any automatic Error Correction.
///
/// A 32-bit read returns 24-bits of raw data from the OTP word.
pub const OTP_DATA_RAW_BASE: *const u32 = 0x4013_4000 as *const u32;
/// How many pages in OTP (post error-correction)
pub const NUM_PAGES: usize = 64;
/// How many rows in one page in OTP (post error-correction)
pub const NUM_ROWS_PER_PAGE: usize = 64;
/// How many rows in OTP (post error-correction)
pub const NUM_ROWS: usize = NUM_PAGES * NUM_ROWS_PER_PAGE;
/// Read one ECC protected word from the OTP
pub fn read_ecc_word(row: usize) -> Result<u16, Error> {
if row >= NUM_ROWS {
return Err(Error::InvalidIndex);
}
// First do a raw read to check permissions
let _ = read_raw_word(row)?;
// One 32-bit read gets us two rows
let offset = row >> 1;
// # Safety
//
// We checked this offset was in range already.
let value = unsafe { OTP_DATA_BASE.add(offset).read() };
if (row & 1) == 0 {
Ok(value as u16)
} else {
Ok((value >> 16) as u16)
}
}
/// Read one raw word from the OTP
///
/// You get the 24-bit raw value in the lower part of the 32-bit result.
pub fn read_raw_word(row: usize) -> Result<u32, Error> {
if row >= NUM_ROWS {
return Err(Error::InvalidIndex);
}
// One 32-bit read gets us one row
// # Safety
//
// We checked this offset was in range already.
let value = unsafe { OTP_DATA_RAW_BASE.add(row).read() };
if value == 0xFFFF_FFFF {
Err(Error::InvalidPermissions)
} else {
Ok(value)
}
}
/// Get the random 64bit chipid from rows 0x0-0x3.
pub fn get_chipid() -> Result<u64, Error> {
let w0 = read_ecc_word(0x000)?.to_be_bytes();
let w1 = read_ecc_word(0x001)?.to_be_bytes();
let w2 = read_ecc_word(0x002)?.to_be_bytes();
let w3 = read_ecc_word(0x003)?.to_be_bytes();
Ok(u64::from_be_bytes([
w3[0], w3[1], w2[0], w2[1], w1[0], w1[1], w0[0], w0[1],
]))
}
/// Get the 128bit private random number from rows 0x4-0xb.
///
/// This ID is not exposed through the USB PICOBOOT GET_INFO command
/// or the ROM get_sys_info() API. However note that the USB PICOBOOT OTP
/// access point can read the entirety of page 0, so this value is not
/// meaningfully private unless the USB PICOBOOT interface is disabled via the
//// DISABLE_BOOTSEL_USB_PICOBOOT_IFC flag in BOOT_FLAGS0
pub fn get_private_random_number() -> Result<u128, Error> {
let w0 = read_ecc_word(0x004)?.to_be_bytes();
let w1 = read_ecc_word(0x005)?.to_be_bytes();
let w2 = read_ecc_word(0x006)?.to_be_bytes();
let w3 = read_ecc_word(0x007)?.to_be_bytes();
let w4 = read_ecc_word(0x008)?.to_be_bytes();
let w5 = read_ecc_word(0x009)?.to_be_bytes();
let w6 = read_ecc_word(0x00a)?.to_be_bytes();
let w7 = read_ecc_word(0x00b)?.to_be_bytes();
Ok(u128::from_be_bytes([
w7[0], w7[1], w6[0], w6[1], w5[0], w5[1], w4[0], w4[1], w3[0], w3[1], w2[0], w2[1], w1[0], w1[1], w0[0], w0[1],
]))
}

View File

@ -5,7 +5,7 @@ use core::pin::Pin as FuturePin;
use core::sync::atomic::{compiler_fence, Ordering};
use core::task::{Context, Poll};
use atomic_polyfill::{AtomicU32, AtomicU8};
use atomic_polyfill::{AtomicU64, AtomicU8};
use embassy_hal_internal::{into_ref, Peripheral, PeripheralRef};
use embassy_sync::waitqueue::AtomicWaker;
use fixed::types::extra::U8;
@ -731,6 +731,8 @@ impl<'d, PIO: Instance + 'd, const SM: usize> StateMachine<'d, PIO, SM> {
w.set_autopull(config.shift_out.auto_fill);
w.set_autopush(config.shift_in.auto_fill);
});
#[cfg(feature = "rp2040")]
sm.pinctrl().write(|w| {
w.set_sideset_count(config.pins.sideset_count);
w.set_set_count(config.pins.set_count);
@ -740,6 +742,52 @@ impl<'d, PIO: Instance + 'd, const SM: usize> StateMachine<'d, PIO, SM> {
w.set_set_base(config.pins.set_base);
w.set_out_base(config.pins.out_base);
});
#[cfg(feature = "_rp235x")]
{
let mut low_ok = true;
let mut high_ok = true;
let in_pins = config.pins.in_base..config.pins.in_base + config.in_count;
let side_pins = config.pins.sideset_base..config.pins.sideset_base + config.pins.sideset_count;
let set_pins = config.pins.set_base..config.pins.set_base + config.pins.set_count;
let out_pins = config.pins.out_base..config.pins.out_base + config.pins.out_count;
for pin_range in [in_pins, side_pins, set_pins, out_pins] {
for pin in pin_range {
low_ok &= pin < 32;
high_ok &= pin >= 16;
}
}
if !low_ok && !high_ok {
panic!(
"All pins must either be <32 or >=16, in:{:?}-{:?}, side:{:?}-{:?}, set:{:?}-{:?}, out:{:?}-{:?}",
config.pins.in_base,
config.pins.in_base + config.in_count - 1,
config.pins.sideset_base,
config.pins.sideset_base + config.pins.sideset_count - 1,
config.pins.set_base,
config.pins.set_base + config.pins.set_count - 1,
config.pins.out_base,
config.pins.out_base + config.pins.out_count - 1,
)
}
let shift = if low_ok { 0 } else { 16 };
sm.pinctrl().write(|w| {
w.set_sideset_count(config.pins.sideset_count);
w.set_set_count(config.pins.set_count);
w.set_out_count(config.pins.out_count);
w.set_in_base(config.pins.in_base.checked_sub(shift).unwrap_or_default());
w.set_sideset_base(config.pins.sideset_base.checked_sub(shift).unwrap_or_default());
w.set_set_base(config.pins.set_base.checked_sub(shift).unwrap_or_default());
w.set_out_base(config.pins.out_base.checked_sub(shift).unwrap_or_default());
});
PIO::PIO.gpiobase().write(|w| w.set_gpiobase(shift == 16));
}
if let Some(origin) = config.origin {
unsafe { instr::exec_jmp(self, origin) }
}
@ -1006,6 +1054,10 @@ impl<'d, PIO: Instance> Common<'d, PIO> {
pub fn make_pio_pin(&mut self, pin: impl Peripheral<P = impl PioPin + 'd> + 'd) -> Pin<'d, PIO> {
into_ref!(pin);
pin.gpio().ctrl().write(|w| w.set_funcsel(PIO::FUNCSEL as _));
#[cfg(feature = "_rp235x")]
pin.pad_ctrl().modify(|w| {
w.set_iso(false);
});
// we can be relaxed about this because we're &mut here and nothing is cached
PIO::state().used_pins.fetch_or(1 << pin.pin_bank(), Ordering::Relaxed);
Pin {
@ -1187,7 +1239,7 @@ impl<'d, PIO: Instance> Pio<'d, PIO> {
// other way.
pub struct State {
users: AtomicU8,
used_pins: AtomicU32,
used_pins: AtomicU64,
}
fn on_pio_drop<PIO: Instance>() {
@ -1195,8 +1247,7 @@ fn on_pio_drop<PIO: Instance>() {
if state.users.fetch_sub(1, Ordering::AcqRel) == 1 {
let used_pins = state.used_pins.load(Ordering::Relaxed);
let null = pac::io::vals::Gpio0ctrlFuncsel::NULL as _;
// we only have 30 pins. don't test the other two since gpio() asserts.
for i in 0..30 {
for i in 0..crate::gpio::BANK0_PIN_COUNT {
if used_pins & (1 << i) != 0 {
pac::IO_BANK0.gpio(i).ctrl().write(|w| w.set_funcsel(null));
}
@ -1221,7 +1272,7 @@ trait SealedInstance {
fn state() -> &'static State {
static STATE: State = State {
users: AtomicU8::new(0),
used_pins: AtomicU32::new(0),
used_pins: AtomicU64::new(0),
};
&STATE

View File

@ -106,6 +106,10 @@ impl<'d> Pwm<'d> {
if let Some(pin) = &a {
pin.gpio().ctrl().write(|w| w.set_funcsel(4));
#[cfg(feature = "_rp235x")]
pin.pad_ctrl().modify(|w| {
w.set_iso(false);
});
}
if let Some(pin) = &b {
pin.gpio().ctrl().write(|w| w.set_funcsel(4));

View File

@ -0,0 +1,33 @@
#![cfg_attr(
feature = "rp2040",
doc = r"
//! Functions and data from the RPI Bootrom.
//!
//! From the [RP2040 datasheet](https://datasheets.raspberrypi.org/rp2040/rp2040-datasheet.pdf), Section 2.8.2.1:
//!
//! > The Bootrom contains a number of public functions that provide useful
//! > RP2040 functionality that might be needed in the absence of any other code
//! > on the device, as well as highly optimized versions of certain key
//! > functionality that would otherwise have to take up space in most user
//! > binaries.
"
)]
#![cfg_attr(
feature = "_rp235x",
doc = r"
//! Functions and data from the RPI Bootrom.
//!
//! From [Section 5.4](https://rptl.io/rp2350-datasheet#section_bootrom) of the
//! RP2350 datasheet:
//!
//! > Whilst some ROM space is dedicated to the implementation of the boot
//! > sequence and USB/UART boot interfaces, the bootrom also contains public
//! > functions that provide useful RP2350 functionality that may be useful for
//! > any code or runtime running on the device
"
)]
#[cfg_attr(feature = "rp2040", path = "rp2040.rs")]
#[cfg_attr(feature = "_rp235x", path = "rp235x.rs")]
mod inner;
pub use inner::*;

View File

@ -189,7 +189,7 @@ macro_rules! rom_functions {
declare_rom_function! {
$(#[$outer])*
fn $name( $($argname: $ty),* ) -> $ret {
$crate::rom_data::rom_table_lookup($crate::rom_data::FUNC_TABLE, *$c)
$crate::rom_data::inner::rom_table_lookup($crate::rom_data::inner::FUNC_TABLE, *$c)
}
}
@ -205,7 +205,7 @@ macro_rules! rom_functions {
declare_rom_function! {
$(#[$outer])*
unsafe fn $name( $($argname: $ty),* ) -> $ret {
$crate::rom_data::rom_table_lookup($crate::rom_data::FUNC_TABLE, *$c)
$crate::rom_data::inner::rom_table_lookup($crate::rom_data::inner::FUNC_TABLE, *$c)
}
}

View File

@ -0,0 +1,752 @@
//! Functions and data from the RPI Bootrom.
//!
//! From [Section 5.4](https://rptl.io/rp2350-datasheet#section_bootrom) of the
//! RP2350 datasheet:
//!
//! > Whilst some ROM space is dedicated to the implementation of the boot
//! > sequence and USB/UART boot interfaces, the bootrom also contains public
//! > functions that provide useful RP2350 functionality that may be useful for
//! > any code or runtime running on the device
// Credit: taken from `rp-hal` (also licensed Apache+MIT)
// https://github.com/rp-rs/rp-hal/blob/main/rp235x-hal/src/rom_data.rs
/// A bootrom function table code.
pub type RomFnTableCode = [u8; 2];
/// This function searches for the tag which matches the mask.
type RomTableLookupFn = unsafe extern "C" fn(code: u32, mask: u32) -> usize;
/// Pointer to the value lookup function supplied by the ROM.
///
/// This address is described at `5.5.1. Locating the API Functions`
#[cfg(all(target_arch = "arm", target_os = "none"))]
const ROM_TABLE_LOOKUP_A2: *const u16 = 0x0000_0016 as _;
/// Pointer to the value lookup function supplied by the ROM.
///
/// This address is described at `5.5.1. Locating the API Functions`
#[cfg(all(target_arch = "arm", target_os = "none"))]
const ROM_TABLE_LOOKUP_A1: *const u32 = 0x0000_0018 as _;
/// Pointer to the data lookup function supplied by the ROM.
///
/// On Arm, the same function is used to look up code and data.
#[cfg(all(target_arch = "arm", target_os = "none"))]
const ROM_DATA_LOOKUP_A2: *const u16 = ROM_TABLE_LOOKUP_A2;
/// Pointer to the data lookup function supplied by the ROM.
///
/// On Arm, the same function is used to look up code and data.
#[cfg(all(target_arch = "arm", target_os = "none"))]
const ROM_DATA_LOOKUP_A1: *const u32 = ROM_TABLE_LOOKUP_A1;
/// Pointer to the value lookup function supplied by the ROM.
///
/// This address is described at `5.5.1. Locating the API Functions`
#[cfg(not(all(target_arch = "arm", target_os = "none")))]
const ROM_TABLE_LOOKUP_A2: *const u16 = 0x0000_7DFA as _;
/// Pointer to the value lookup function supplied by the ROM.
///
/// This address is described at `5.5.1. Locating the API Functions`
#[cfg(not(all(target_arch = "arm", target_os = "none")))]
const ROM_TABLE_LOOKUP_A1: *const u32 = 0x0000_7DF8 as _;
/// Pointer to the data lookup function supplied by the ROM.
///
/// On RISC-V, a different function is used to look up data.
#[cfg(not(all(target_arch = "arm", target_os = "none")))]
const ROM_DATA_LOOKUP_A2: *const u16 = 0x0000_7DF8 as _;
/// Pointer to the data lookup function supplied by the ROM.
///
/// On RISC-V, a different function is used to look up data.
#[cfg(not(all(target_arch = "arm", target_os = "none")))]
const ROM_DATA_LOOKUP_A1: *const u32 = 0x0000_7DF4 as _;
/// Address of the version number of the ROM.
const VERSION_NUMBER: *const u8 = 0x0000_0013 as _;
#[allow(unused)]
mod rt_flags {
pub const FUNC_RISCV: u32 = 0x0001;
pub const FUNC_RISCV_FAR: u32 = 0x0003;
pub const FUNC_ARM_SEC: u32 = 0x0004;
// reserved for 32-bit pointer: 0x0008
pub const FUNC_ARM_NONSEC: u32 = 0x0010;
// reserved for 32-bit pointer: 0x0020
pub const DATA: u32 = 0x0040;
// reserved for 32-bit pointer: 0x0080
#[cfg(all(target_arch = "arm", target_os = "none"))]
pub const FUNC_ARM_SEC_RISCV: u32 = FUNC_ARM_SEC;
#[cfg(not(all(target_arch = "arm", target_os = "none")))]
pub const FUNC_ARM_SEC_RISCV: u32 = FUNC_RISCV;
}
/// Retrieve rom content from a table using a code.
pub fn rom_table_lookup(tag: RomFnTableCode, mask: u32) -> usize {
let tag = u16::from_le_bytes(tag) as u32;
unsafe {
let lookup_func = if rom_version_number() == 1 {
ROM_TABLE_LOOKUP_A1.read() as usize
} else {
ROM_TABLE_LOOKUP_A2.read() as usize
};
let lookup_func: RomTableLookupFn = core::mem::transmute(lookup_func);
lookup_func(tag, mask)
}
}
/// Retrieve rom data content from a table using a code.
pub fn rom_data_lookup(tag: RomFnTableCode, mask: u32) -> usize {
let tag = u16::from_le_bytes(tag) as u32;
unsafe {
let lookup_func = if rom_version_number() == 1 {
ROM_DATA_LOOKUP_A1.read() as usize
} else {
ROM_DATA_LOOKUP_A2.read() as usize
};
let lookup_func: RomTableLookupFn = core::mem::transmute(lookup_func);
lookup_func(tag, mask)
}
}
macro_rules! declare_rom_function {
(
$(#[$outer:meta])*
fn $name:ident( $($argname:ident: $ty:ty),* ) -> $ret:ty
$lookup:block
) => {
#[doc = r"Additional access for the `"]
#[doc = stringify!($name)]
#[doc = r"` ROM function."]
pub mod $name {
/// Retrieve a function pointer.
#[cfg(not(feature = "rom-func-cache"))]
pub fn ptr() -> extern "C" fn( $($argname: $ty),* ) -> $ret {
let p: usize = $lookup;
unsafe {
let func : extern "C" fn( $($argname: $ty),* ) -> $ret = core::mem::transmute(p);
func
}
}
/// Retrieve a function pointer.
#[cfg(feature = "rom-func-cache")]
pub fn ptr() -> extern "C" fn( $($argname: $ty),* ) -> $ret {
use core::sync::atomic::{AtomicU16, Ordering};
// All pointers in the ROM fit in 16 bits, so we don't need a
// full width word to store the cached value.
static CACHED_PTR: AtomicU16 = AtomicU16::new(0);
// This is safe because the lookup will always resolve
// to the same value. So even if an interrupt or another
// core starts at the same time, it just repeats some
// work and eventually writes back the correct value.
let p: usize = match CACHED_PTR.load(Ordering::Relaxed) {
0 => {
let raw: usize = $lookup;
CACHED_PTR.store(raw as u16, Ordering::Relaxed);
raw
},
val => val as usize,
};
unsafe {
let func : extern "C" fn( $($argname: $ty),* ) -> $ret = core::mem::transmute(p);
func
}
}
}
$(#[$outer])*
pub extern "C" fn $name( $($argname: $ty),* ) -> $ret {
$name::ptr()($($argname),*)
}
};
(
$(#[$outer:meta])*
unsafe fn $name:ident( $($argname:ident: $ty:ty),* ) -> $ret:ty
$lookup:block
) => {
#[doc = r"Additional access for the `"]
#[doc = stringify!($name)]
#[doc = r"` ROM function."]
pub mod $name {
/// Retrieve a function pointer.
#[cfg(not(feature = "rom-func-cache"))]
pub fn ptr() -> unsafe extern "C" fn( $($argname: $ty),* ) -> $ret {
let p: usize = $lookup;
unsafe {
let func : unsafe extern "C" fn( $($argname: $ty),* ) -> $ret = core::mem::transmute(p);
func
}
}
/// Retrieve a function pointer.
#[cfg(feature = "rom-func-cache")]
pub fn ptr() -> unsafe extern "C" fn( $($argname: $ty),* ) -> $ret {
use core::sync::atomic::{AtomicU16, Ordering};
// All pointers in the ROM fit in 16 bits, so we don't need a
// full width word to store the cached value.
static CACHED_PTR: AtomicU16 = AtomicU16::new(0);
// This is safe because the lookup will always resolve
// to the same value. So even if an interrupt or another
// core starts at the same time, it just repeats some
// work and eventually writes back the correct value.
let p: usize = match CACHED_PTR.load(Ordering::Relaxed) {
0 => {
let raw: usize = $lookup;
CACHED_PTR.store(raw as u16, Ordering::Relaxed);
raw
},
val => val as usize,
};
unsafe {
let func : unsafe extern "C" fn( $($argname: $ty),* ) -> $ret = core::mem::transmute(p);
func
}
}
}
$(#[$outer])*
/// # Safety
///
/// This is a low-level C function. It may be difficult to call safely from
/// Rust. If in doubt, check the rp235x datasheet for details and do your own
/// safety evaluation.
pub unsafe extern "C" fn $name( $($argname: $ty),* ) -> $ret {
$name::ptr()($($argname),*)
}
};
}
// **************** 5.5.7 Low-level Flash Commands ****************
declare_rom_function! {
/// Restore all QSPI pad controls to their default state, and connect the
/// QMI peripheral to the QSPI pads.
///
/// Supported architectures: ARM-S, RISC-V
unsafe fn connect_internal_flash() -> () {
crate::rom_data::rom_table_lookup(*b"IF", crate::rom_data::inner::rt_flags::FUNC_ARM_SEC_RISCV)
}
}
declare_rom_function! {
/// Initialise the QMI for serial operations (direct mode)
///
/// Also initialise a basic XIP mode, where the QMI will perform 03h serial
/// read commands at low speed (CLKDIV=12) in response to XIP reads.
///
/// Then, issue a sequence to the QSPI device on chip select 0, designed to
/// return it from continuous read mode ("XIP mode") and/or QPI mode to a
/// state where it will accept serial commands. This is necessary after
/// system reset to restore the QSPI device to a known state, because
/// resetting RP2350 does not reset attached QSPI devices. It is also
/// necessary when user code, having already performed some
/// continuous-read-mode or QPI-mode accesses, wishes to return the QSPI
/// device to a state where it will accept the serial erase and programming
/// commands issued by the bootroms flash access functions.
///
/// If a GPIO for the secondary chip select is configured via FLASH_DEVINFO,
/// then the XIP exit sequence is also issued to chip select 1.
///
/// The QSPI device should be accessible for XIP reads after calling this
/// function; the name flash_exit_xip refers to returning the QSPI device
/// from its XIP state to a serial command state.
///
/// Supported architectures: ARM-S, RISC-V
unsafe fn flash_exit_xip() -> () {
crate::rom_data::rom_table_lookup(*b"EX", crate::rom_data::inner::rt_flags::FUNC_ARM_SEC_RISCV)
}
}
declare_rom_function! {
/// Erase count bytes, starting at addr (offset from start of flash).
///
/// Optionally, pass a block erase command e.g. D8h block erase, and the
/// size of the block erased by this command — this function will use the
/// larger block erase where possible, for much higher erase speed. addr
/// must be aligned to a 4096-byte sector, and count must be a multiple of
/// 4096 bytes.
///
/// This is a low-level flash API, and no validation of the arguments is
/// performed. See flash_op() for a higher-level API which checks alignment,
/// flash bounds and partition permissions, and can transparently apply a
/// runtime-to-storage address translation.
///
/// The QSPI device must be in a serial command state before calling this
/// API, which can be achieved by calling connect_internal_flash() followed
/// by flash_exit_xip(). After the erase, the flash cache should be flushed
/// via flash_flush_cache() to ensure the modified flash data is visible to
/// cached XIP accesses.
///
/// Finally, the original XIP mode should be restored by copying the saved
/// XIP setup function from bootram into SRAM, and executing it: the bootrom
/// provides a default function which restores the flash mode/clkdiv
/// discovered during flash scanning, and user programs can override this
/// with their own XIP setup function.
///
/// For the duration of the erase operation, QMI is in direct mode (Section
/// 12.14.5) and attempting to access XIP from DMA, the debugger or the
/// other core will return a bus fault. XIP becomes accessible again once
/// the function returns.
///
/// Supported architectures: ARM-S, RISC-V
unsafe fn flash_range_erase(addr: u32, count: usize, block_size: u32, block_cmd: u8) -> () {
crate::rom_data::rom_table_lookup(*b"RE", crate::rom_data::inner::rt_flags::FUNC_ARM_SEC_RISCV)
}
}
declare_rom_function! {
/// Program data to a range of flash storage addresses starting at addr
/// (offset from the start of flash) and count bytes in size.
///
/// `addr` must be aligned to a 256-byte boundary, and count must be a
/// multiple of 256.
///
/// This is a low-level flash API, and no validation of the arguments is
/// performed. See flash_op() for a higher-level API which checks alignment,
/// flash bounds and partition permissions, and can transparently apply a
/// runtime-to-storage address translation.
///
/// The QSPI device must be in a serial command state before calling this
/// API — see notes on flash_range_erase().
///
/// Supported architectures: ARM-S, RISC-V
unsafe fn flash_range_program(addr: u32, data: *const u8, count: usize) -> () {
crate::rom_data::rom_table_lookup(*b"RP", crate::rom_data::inner::rt_flags::FUNC_ARM_SEC_RISCV)
}
}
declare_rom_function! {
/// Flush the entire XIP cache, by issuing an invalidate by set/way
/// maintenance operation to every cache line (Section 4.4.1).
///
/// This ensures that flash program/erase operations are visible to
/// subsequent cached XIP reads.
///
/// Note that this unpins pinned cache lines, which may interfere with
/// cache-as-SRAM use of the XIP cache.
///
/// No other operations are performed.
///
/// Supported architectures: ARM-S, RISC-V
unsafe fn flash_flush_cache() -> () {
crate::rom_data::rom_table_lookup(*b"FC", crate::rom_data::inner::rt_flags::FUNC_ARM_SEC_RISCV)
}
}
declare_rom_function! {
/// Configure the QMI to generate a standard 03h serial read command, with
/// 24 address bits, upon each XIP access.
///
/// This is a slow XIP configuration, but is widely supported. CLKDIV is set
/// to 12. The debugger may call this function to ensure that flash is
/// readable following a program/erase operation.
///
/// Note that the same setup is performed by flash_exit_xip(), and the
/// RP2350 flash program/erase functions do not leave XIP in an inaccessible
/// state, so calls to this function are largely redundant. It is provided
/// for compatibility with RP2040.
///
/// Supported architectures: ARM-S, RISC-V
unsafe fn flash_enter_cmd_xip() -> () {
crate::rom_data::rom_table_lookup(*b"CX", crate::rom_data::inner::rt_flags::FUNC_ARM_SEC_RISCV)
}
}
declare_rom_function! {
/// Configure QMI for one of a small menu of XIP read modes supported by the
/// bootrom. This mode is configured for both memory windows (both chip
/// selects), and the clock divisor is also applied to direct mode.
///
/// The available modes are:
///
/// * 0: `03h` serial read: serial address, serial data, no wait cycles
/// * 1: `0Bh` serial read: serial address, serial data, 8 wait cycles
/// * 2: `BBh` dual-IO read: dual address, dual data, 4 wait cycles
/// (including MODE bits, which are driven to 0)
/// * 3: `EBh` quad-IO read: quad address, quad data, 6 wait cycles
/// (including MODE bits, which are driven to 0)
///
/// The XIP write command/format are not configured by this function. When
/// booting from flash, the bootrom tries each of these modes in turn, from
/// 3 down to 0. The first mode that is found to work is remembered, and a
/// default XIP setup function is written into bootram that calls this
/// function (flash_select_xip_read_mode) with the parameters discovered
/// during flash scanning. This can be called at any time to restore the
/// flash parameters discovered during flash boot.
///
/// All XIP modes configured by the bootrom have an 8-bit serial command
/// prefix, so that the flash can remain in a serial command state, meaning
/// XIP accesses can be mixed more freely with program/erase serial
/// operations. This has a performance penalty, so users can perform their
/// own flash setup after flash boot using continuous read mode or QPI mode
/// to avoid or alleviate the command prefix cost.
///
/// Supported architectures: ARM-S, RISC-V
unsafe fn flash_select_xip_read_mode(bootrom_xip_mode: u8, clkdiv: u8) -> () {
crate::rom_data::rom_table_lookup(*b"XM", crate::rom_data::inner::rt_flags::FUNC_ARM_SEC_RISCV)
}
}
declare_rom_function! {
/// Restore the QMI address translation registers, ATRANS0 through ATRANS7,
/// to their reset state. This makes the runtime- to-storage address map an
/// identity map, i.e. the mapped and unmapped address are equal, and the
/// entire space is fully mapped.
///
/// See [Section 12.14.4](https://rptl.io/rp2350-datasheet#section_bootrom) of the RP2350
/// datasheet.
///
/// Supported architectures: ARM-S, RISC-V
unsafe fn flash_reset_address_trans() -> () {
crate::rom_data::rom_table_lookup(*b"RA", crate::rom_data::inner::rt_flags::FUNC_ARM_SEC_RISCV)
}
}
// **************** High-level Flash Commands ****************
declare_rom_function! {
/// Applies the address translation currently configured by QMI address
/// translation registers, ATRANS0 through ATRANS7.
///
/// See [Section 12.14.4](https://rptl.io/rp2350-datasheet#section_bootrom) of the RP2350
/// datasheet.
///
/// Translating an address outside of the XIP runtime address window, or
/// beyond the bounds of an ATRANSx_SIZE field, returns
/// BOOTROM_ERROR_INVALID_ADDRESS, which is not a valid flash storage
/// address. Otherwise, return the storage address which QMI would access
/// when presented with the runtime address addr. This is effectively a
/// virtual-to-physical address translation for QMI.
///
/// Supported architectures: ARM-S, RISC-V
unsafe fn flash_runtime_to_storage_addr(addr: u32) -> i32 {
crate::rom_data::rom_table_lookup(*b"FA", crate::rom_data::inner::rt_flags::FUNC_ARM_SEC_RISCV)
}
}
declare_rom_function! {
/// Non-secure version of [flash_runtime_to_storage_addr()]
///
/// Supported architectures: ARM-NS
#[cfg(all(target_arch = "arm", target_os = "none"))]
unsafe fn flash_runtime_to_storage_addr_ns(addr: u32) -> i32 {
crate::rom_data::rom_table_lookup(*b"FA", crate::rom_data::inner::rt_flags::FUNC_ARM_NONSEC)
}
}
declare_rom_function! {
/// Perform a flash read, erase, or program operation.
///
/// Erase operations must be sector-aligned (4096 bytes) and sector-
/// multiple-sized, and program operations must be page-aligned (256 bytes)
/// and page-multiple-sized; misaligned erase and program operations will
/// return BOOTROM_ERROR_BAD_ALIGNMENT. The operation — erase, read, program
/// — is selected by the CFLASH_OP_BITS bitfield of the flags argument.
///
/// See datasheet section 5.5.8.2 for more details.
///
/// Supported architectures: ARM-S, RISC-V
unsafe fn flash_op(flags: u32, addr: u32, size_bytes: u32, buffer: *mut u8) -> i32 {
crate::rom_data::rom_table_lookup(*b"FO", crate::rom_data::inner::rt_flags::FUNC_ARM_SEC_RISCV)
}
}
declare_rom_function! {
/// Non-secure version of [flash_op()]
///
/// Supported architectures: ARM-NS
#[cfg(all(target_arch = "arm", target_os = "none"))]
unsafe fn flash_op_ns(flags: u32, addr: u32, size_bytes: u32, buffer: *mut u8) -> i32 {
crate::rom_data::rom_table_lookup(*b"FO", crate::rom_data::inner::rt_flags::FUNC_ARM_NONSEC)
}
}
// **************** Security Related Functions ****************
declare_rom_function! {
/// Allow or disallow the specific NS API (note all NS APIs default to
/// disabled).
///
/// See datasheet section 5.5.9.1 for more details.
///
/// Supported architectures: ARM-S
#[cfg(all(target_arch = "arm", target_os = "none"))]
unsafe fn set_ns_api_permission(ns_api_num: u32, allowed: u8) -> i32 {
crate::rom_data::rom_table_lookup(*b"SP", crate::rom_data::inner::rt_flags::FUNC_ARM_SEC)
}
}
declare_rom_function! {
/// Utility method that can be used by secure ARM code to validate a buffer
/// passed to it from Non-secure code.
///
/// See datasheet section 5.5.9.2 for more details.
///
/// Supported architectures: ARM-S, RISC-V
unsafe fn validate_ns_buffer() -> () {
crate::rom_data::rom_table_lookup(*b"VB", crate::rom_data::inner::rt_flags::FUNC_ARM_SEC_RISCV)
}
}
// **************** Miscellaneous Functions ****************
declare_rom_function! {
/// Resets the RP2350 and uses the watchdog facility to restart.
///
/// See datasheet section 5.5.10.1 for more details.
///
/// Supported architectures: ARM-S, RISC-V
fn reboot(flags: u32, delay_ms: u32, p0: u32, p1: u32) -> i32 {
crate::rom_data::rom_table_lookup(*b"RB", crate::rom_data::inner::rt_flags::FUNC_ARM_SEC_RISCV)
}
}
declare_rom_function! {
/// Non-secure version of [reboot()]
///
/// Supported architectures: ARM-NS
#[cfg(all(target_arch = "arm", target_os = "none"))]
fn reboot_ns(flags: u32, delay_ms: u32, p0: u32, p1: u32) -> i32 {
crate::rom_data::rom_table_lookup(*b"RB", crate::rom_data::inner::rt_flags::FUNC_ARM_NONSEC)
}
}
declare_rom_function! {
/// Resets internal bootrom state.
///
/// See datasheet section 5.5.10.2 for more details.
///
/// Supported architectures: ARM-S, RISC-V
unsafe fn bootrom_state_reset(flags: u32) -> () {
crate::rom_data::rom_table_lookup(*b"SR", crate::rom_data::inner::rt_flags::FUNC_ARM_SEC_RISCV)
}
}
declare_rom_function! {
/// Set a boot ROM callback.
///
/// The only supported callback_number is 0 which sets the callback used for
/// the secure_call API.
///
/// See datasheet section 5.5.10.3 for more details.
///
/// Supported architectures: ARM-S, RISC-V
unsafe fn set_rom_callback(callback_number: i32, callback_fn: *const ()) -> i32 {
crate::rom_data::rom_table_lookup(*b"RC", crate::rom_data::inner::rt_flags::FUNC_ARM_SEC_RISCV)
}
}
// **************** System Information Functions ****************
declare_rom_function! {
/// Fills a buffer with various system information.
///
/// Note that this API is also used to return information over the PICOBOOT
/// interface.
///
/// See datasheet section 5.5.11.1 for more details.
///
/// Supported architectures: ARM-S, RISC-V
unsafe fn get_sys_info(out_buffer: *mut u32, out_buffer_word_size: usize, flags: u32) -> i32 {
crate::rom_data::rom_table_lookup(*b"GS", crate::rom_data::inner::rt_flags::FUNC_ARM_SEC_RISCV)
}
}
declare_rom_function! {
/// Non-secure version of [get_sys_info()]
///
/// Supported architectures: ARM-NS
#[cfg(all(target_arch = "arm", target_os = "none"))]
unsafe fn get_sys_info_ns(out_buffer: *mut u32, out_buffer_word_size: usize, flags: u32) -> i32 {
crate::rom_data::rom_table_lookup(*b"GS", crate::rom_data::inner::rt_flags::FUNC_ARM_NONSEC)
}
}
declare_rom_function! {
/// Fills a buffer with information from the partition table.
///
/// Note that this API is also used to return information over the PICOBOOT
/// interface.
///
/// See datasheet section 5.5.11.2 for more details.
///
/// Supported architectures: ARM-S, RISC-V
unsafe fn get_partition_table_info(out_buffer: *mut u32, out_buffer_word_size: usize, flags_and_partition: u32) -> i32 {
crate::rom_data::rom_table_lookup(*b"GP", crate::rom_data::inner::rt_flags::FUNC_ARM_SEC_RISCV)
}
}
declare_rom_function! {
/// Non-secure version of [get_partition_table_info()]
///
/// Supported architectures: ARM-NS
#[cfg(all(target_arch = "arm", target_os = "none"))]
unsafe fn get_partition_table_info_ns(out_buffer: *mut u32, out_buffer_word_size: usize, flags_and_partition: u32) -> i32 {
crate::rom_data::rom_table_lookup(*b"GP", crate::rom_data::inner::rt_flags::FUNC_ARM_NONSEC)
}
}
declare_rom_function! {
/// Loads the current partition table from flash, if present.
///
/// See datasheet section 5.5.11.3 for more details.
///
/// Supported architectures: ARM-S, RISC-V
unsafe fn load_partition_table(workarea_base: *mut u8, workarea_size: usize, force_reload: bool) -> i32 {
crate::rom_data::rom_table_lookup(*b"LP", crate::rom_data::inner::rt_flags::FUNC_ARM_SEC_RISCV)
}
}
declare_rom_function! {
/// Writes data from a buffer into OTP, or reads data from OTP into a buffer.
///
/// See datasheet section 5.5.11.4 for more details.
///
/// Supported architectures: ARM-S, RISC-V
unsafe fn otp_access(buf: *mut u8, buf_len: usize, row_and_flags: u32) -> i32 {
crate::rom_data::rom_table_lookup(*b"OA", crate::rom_data::inner::rt_flags::FUNC_ARM_SEC_RISCV)
}
}
declare_rom_function! {
/// Non-secure version of [otp_access()]
///
/// Supported architectures: ARM-NS
#[cfg(all(target_arch = "arm", target_os = "none"))]
unsafe fn otp_access_ns(buf: *mut u8, buf_len: usize, row_and_flags: u32) -> i32 {
crate::rom_data::rom_table_lookup(*b"OA", crate::rom_data::inner::rt_flags::FUNC_ARM_NONSEC)
}
}
// **************** Boot Related Functions ****************
declare_rom_function! {
/// Determines which of the partitions has the "better" IMAGE_DEF. In the
/// case of executable images, this is the one that would be booted.
///
/// See datasheet section 5.5.12.1 for more details.
///
/// Supported architectures: ARM-S, RISC-V
unsafe fn pick_ab_parition(workarea_base: *mut u8, workarea_size: usize, partition_a_num: u32) -> i32 {
crate::rom_data::rom_table_lookup(*b"AB", crate::rom_data::inner::rt_flags::FUNC_ARM_SEC_RISCV)
}
}
declare_rom_function! {
/// Searches a memory region for a launchable image, and executes it if
/// possible.
///
/// See datasheet section 5.5.12.2 for more details.
///
/// Supported architectures: ARM-S, RISC-V
unsafe fn chain_image(workarea_base: *mut u8, workarea_size: usize, region_base: i32, region_size: u32) -> i32 {
crate::rom_data::rom_table_lookup(*b"CI", crate::rom_data::inner::rt_flags::FUNC_ARM_SEC_RISCV)
}
}
declare_rom_function! {
/// Perform an "explicit" buy of an executable launched via an IMAGE_DEF
/// which was "explicit buy" flagged.
///
/// See datasheet section 5.5.12.3 for more details.
///
/// Supported architectures: ARM-S, RISC-V
unsafe fn explicit_buy(buffer: *mut u8, buffer_size: u32) -> i32 {
crate::rom_data::rom_table_lookup(*b"EB", crate::rom_data::inner::rt_flags::FUNC_ARM_SEC_RISCV)
}
}
declare_rom_function! {
/// Not yet documented.
///
/// See datasheet section 5.5.12.4 for more details.
///
/// Supported architectures: ARM-S, RISC-V
unsafe fn get_uf2_target_partition(workarea_base: *mut u8, workarea_size: usize, family_id: u32, partition_out: *mut u32) -> i32 {
crate::rom_data::rom_table_lookup(*b"GU", crate::rom_data::inner::rt_flags::FUNC_ARM_SEC_RISCV)
}
}
declare_rom_function! {
/// Returns: The index of the B partition of partition A if a partition
/// table is present and loaded, and there is a partition A with a B
/// partition; otherwise returns BOOTROM_ERROR_NOT_FOUND.
///
/// See datasheet section 5.5.12.5 for more details.
///
/// Supported architectures: ARM-S, RISC-V
unsafe fn get_b_partition(partition_a: u32) -> i32 {
crate::rom_data::rom_table_lookup(*b"GB", crate::rom_data::inner::rt_flags::FUNC_ARM_SEC_RISCV)
}
}
// **************** Non-secure-specific Functions ****************
// NB: The "secure_call" function should be here, but it doesn't have a fixed
// function signature as it is designed to let you bounce into any secure
// function from non-secure mode.
// **************** RISC-V Functions ****************
declare_rom_function! {
/// Set stack for RISC-V bootrom functions to use.
///
/// See datasheet section 5.5.14.1 for more details.
///
/// Supported architectures: RISC-V
#[cfg(not(all(target_arch = "arm", target_os = "none")))]
unsafe fn set_bootrom_stack(base_size: *mut u32) -> i32 {
crate::rom_data::rom_table_lookup(*b"SS", crate::rom_data::inner::rt_flags::FUNC_RISCV)
}
}
/// The version number of the rom.
pub fn rom_version_number() -> u8 {
unsafe { *VERSION_NUMBER }
}
/// The 8 most significant hex digits of the Bootrom git revision.
pub fn git_revision() -> u32 {
let ptr = rom_data_lookup(*b"GR", rt_flags::DATA) as *const u32;
unsafe { ptr.read() }
}
/// A pointer to the resident partition table info.
///
/// The resident partition table is the subset of the full partition table that
/// is kept in memory, and used for flash permissions.
pub fn partition_table_pointer() -> *const u32 {
let ptr = rom_data_lookup(*b"PT", rt_flags::DATA) as *const *const u32;
unsafe { ptr.read() }
}
/// Determine if we are in secure mode
///
/// Returns `true` if we are in secure mode and `false` if we are in non-secure
/// mode.
#[cfg(all(target_arch = "arm", target_os = "none"))]
pub fn is_secure_mode() -> bool {
// Look at the start of ROM, which is always readable
#[allow(clippy::zero_ptr)]
let rom_base: *mut u32 = 0x0000_0000 as *mut u32;
// Use the 'tt' instruction to check the permissions for that address
let tt = cortex_m::asm::tt(rom_base);
// Is the secure bit set? => secure mode
(tt & (1 << 22)) != 0
}
/// Determine if we are in secure mode
///
/// Always returns `false` on RISC-V as it is impossible to determine if
/// you are in Machine Mode or User Mode by design.
#[cfg(not(all(target_arch = "arm", target_os = "none")))]
pub fn is_secure_mode() -> bool {
false
}

405
embassy-rp/src/trng.rs Normal file
View File

@ -0,0 +1,405 @@
//! True Random Number Generator (TRNG) driver.
use core::future::poll_fn;
use core::marker::PhantomData;
use core::ops::Not;
use core::task::Poll;
use embassy_hal_internal::Peripheral;
use embassy_sync::waitqueue::AtomicWaker;
use rand_core::Error;
use crate::interrupt::typelevel::{Binding, Interrupt};
use crate::peripherals::TRNG;
use crate::{interrupt, pac};
trait SealedInstance {
fn regs() -> pac::trng::Trng;
fn waker() -> &'static AtomicWaker;
}
/// TRNG peripheral instance.
#[allow(private_bounds)]
pub trait Instance: SealedInstance {
/// Interrupt for this peripheral.
type Interrupt: Interrupt;
}
impl SealedInstance for TRNG {
fn regs() -> rp_pac::trng::Trng {
pac::TRNG
}
fn waker() -> &'static AtomicWaker {
static WAKER: AtomicWaker = AtomicWaker::new();
&WAKER
}
}
impl Instance for TRNG {
type Interrupt = interrupt::typelevel::TRNG_IRQ;
}
#[derive(Copy, Clone, Debug)]
#[allow(missing_docs)]
/// TRNG ROSC Inverter chain length options.
pub enum InverterChainLength {
None = 0,
One,
Two,
Three,
Four,
}
impl From<InverterChainLength> for u8 {
fn from(value: InverterChainLength) -> Self {
value as u8
}
}
/// Configuration for the TRNG.
///
/// - Three built in entropy checks
/// - ROSC frequency controlled by selecting one of ROSC chain lengths
/// - Sample period in terms of system clock ticks
///
///
/// Default configuration is based on the following from documentation:
///
/// ----
///
/// RP2350 Datasheet 12.12.2
///
/// ...
///
/// When configuring the TRNG block, consider the following principles:
/// • As average generation time increases, result quality increases and failed entropy checks decrease.
/// • A low sample count decreases average generation time, but increases the chance of NIST test-failing results and
/// failed entropy checks.
/// For acceptable results with an average generation time of about 2 milliseconds, use ROSC chain length settings of 0 or
/// 1 and sample count settings of 20-25.
///
/// ---
///
/// Note, Pico SDK and Bootrom don't use any of the entropy checks and sample the ROSC directly
/// by setting the sample period to 0. Random data collected this way is then passed through
/// either hardware accelerated SHA256 (Bootrom) or xoroshiro128** (version 1.0!).
#[non_exhaustive]
#[derive(Copy, Clone, Debug)]
pub struct Config {
/// Bypass TRNG autocorrelation test
pub disable_autocorrelation_test: bool,
/// Bypass CRNGT test
pub disable_crngt_test: bool,
/// When set, the Von-Neuman balancer is bypassed (including the
/// 32 consecutive bits test)
pub disable_von_neumann_balancer: bool,
/// Sets the number of rng_clk cycles between two consecutive
/// ring oscillator samples.
/// Note: If the von Neumann decorrelator is bypassed, the minimum value for
/// sample counter must not be less than seventeen
pub sample_count: u32,
/// Selects the number of inverters (out of four possible
/// selections) in the ring oscillator (the entropy source). Higher values select
/// longer inverter chain lengths.
pub inverter_chain_length: InverterChainLength,
}
impl Default for Config {
fn default() -> Self {
Config {
disable_autocorrelation_test: true,
disable_crngt_test: true,
disable_von_neumann_balancer: true,
sample_count: 25,
inverter_chain_length: InverterChainLength::One,
}
}
}
/// True Random Number Generator Driver for RP2350
///
/// This driver provides async and blocking options.
///
/// See [Config] for configuration details.
///
/// Usage example:
/// ```no_run
/// use embassy_executor::Spawner;
/// use embassy_rp::trng::Trng;
/// use embassy_rp::peripherals::TRNG;
/// use embassy_rp::bind_interrupts;
///
/// bind_interrupts!(struct Irqs {
/// TRNG_IRQ => embassy_rp::trng::InterruptHandler<TRNG>;
/// });
///
/// #[embassy_executor::main]
/// async fn main(spawner: Spawner) {
/// let peripherals = embassy_rp::init(Default::default());
/// let mut trng = Trng::new(peripherals.TRNG, Irqs, embassy_rp::trng::Config::default());
///
/// let mut randomness = [0u8; 58];
/// loop {
/// trng.fill_bytes(&mut randomness).await;
/// assert_ne!(randomness, [0u8; 58]);
/// }
///}
/// ```
pub struct Trng<'d, T: Instance> {
phantom: PhantomData<&'d mut T>,
}
/// 12.12.1. Overview
/// On request, the TRNG block generates a block of 192 entropy bits generated by automatically processing a series of
/// periodic samples from the TRNG blocks internal Ring Oscillator (ROSC).
const TRNG_BLOCK_SIZE_BITS: usize = 192;
const TRNG_BLOCK_SIZE_BYTES: usize = TRNG_BLOCK_SIZE_BITS / 8;
impl<'d, T: Instance> Trng<'d, T> {
/// Create a new TRNG driver.
pub fn new(
_trng: impl Peripheral<P = T> + 'd,
_irq: impl Binding<T::Interrupt, InterruptHandler<T>> + 'd,
config: Config,
) -> Self {
let regs = T::regs();
regs.rng_imr().write(|w| w.set_ehr_valid_int_mask(false));
let trng_config_register = regs.trng_config();
trng_config_register.write(|w| {
w.set_rnd_src_sel(config.inverter_chain_length.clone().into());
});
let sample_count_register = regs.sample_cnt1();
sample_count_register.write(|w| {
*w = config.sample_count;
});
let debug_control_register = regs.trng_debug_control();
debug_control_register.write(|w| {
w.set_auto_correlate_bypass(config.disable_autocorrelation_test);
w.set_trng_crngt_bypass(config.disable_crngt_test);
w.set_vnc_bypass(config.disable_von_neumann_balancer)
});
Trng { phantom: PhantomData }
}
fn start_rng(&self) {
let regs = T::regs();
let source_enable_register = regs.rnd_source_enable();
// Enable TRNG ROSC
source_enable_register.write(|w| w.set_rnd_src_en(true));
}
fn stop_rng(&self) {
let regs = T::regs();
let source_enable_register = regs.rnd_source_enable();
source_enable_register.write(|w| w.set_rnd_src_en(false));
let reset_bits_counter_register = regs.rst_bits_counter();
reset_bits_counter_register.write(|w| w.set_rst_bits_counter(true));
}
fn enable_irq(&self) {
unsafe { T::Interrupt::enable() }
}
fn disable_irq(&self) {
T::Interrupt::disable();
}
fn blocking_wait_for_successful_generation(&self) {
let regs = T::regs();
let trng_busy_register = regs.trng_busy();
let trng_valid_register = regs.trng_valid();
let mut success = false;
while success.not() {
while trng_busy_register.read().trng_busy() {}
if trng_valid_register.read().ehr_valid().not() {
if regs.rng_isr().read().autocorr_err() {
regs.trng_sw_reset().write(|w| w.set_trng_sw_reset(true));
} else {
panic!("RNG not busy, but ehr is not valid!")
}
} else {
success = true
}
}
}
fn read_ehr_registers_into_array(&mut self, buffer: &mut [u8; TRNG_BLOCK_SIZE_BYTES]) {
let regs = T::regs();
let ehr_data_regs = [
regs.ehr_data0(),
regs.ehr_data1(),
regs.ehr_data2(),
regs.ehr_data3(),
regs.ehr_data4(),
regs.ehr_data5(),
];
for (i, reg) in ehr_data_regs.iter().enumerate() {
buffer[i * 4..i * 4 + 4].copy_from_slice(&reg.read().to_ne_bytes());
}
}
fn blocking_read_ehr_registers_into_array(&mut self, buffer: &mut [u8; TRNG_BLOCK_SIZE_BYTES]) {
self.blocking_wait_for_successful_generation();
self.read_ehr_registers_into_array(buffer);
}
/// Fill the buffer with random bytes, async version.
pub async fn fill_bytes(&mut self, destination: &mut [u8]) {
if destination.is_empty() {
return; // Nothing to fill
}
self.start_rng();
self.enable_irq();
let mut bytes_transferred = 0usize;
let mut buffer = [0u8; TRNG_BLOCK_SIZE_BYTES];
let regs = T::regs();
let trng_busy_register = regs.trng_busy();
let trng_valid_register = regs.trng_valid();
let waker = T::waker();
let destination_length = destination.len();
poll_fn(|context| {
waker.register(context.waker());
if bytes_transferred == destination_length {
self.stop_rng();
self.disable_irq();
Poll::Ready(())
} else {
if trng_busy_register.read().trng_busy() {
Poll::Pending
} else {
if trng_valid_register.read().ehr_valid().not() {
panic!("RNG not busy, but ehr is not valid!")
}
self.read_ehr_registers_into_array(&mut buffer);
let remaining = destination_length - bytes_transferred;
if remaining > TRNG_BLOCK_SIZE_BYTES {
destination[bytes_transferred..bytes_transferred + TRNG_BLOCK_SIZE_BYTES]
.copy_from_slice(&buffer);
bytes_transferred += TRNG_BLOCK_SIZE_BYTES
} else {
destination[bytes_transferred..bytes_transferred + remaining]
.copy_from_slice(&buffer[0..remaining]);
bytes_transferred += remaining
}
if bytes_transferred == destination_length {
self.stop_rng();
self.disable_irq();
Poll::Ready(())
} else {
Poll::Pending
}
}
}
})
.await
}
/// Fill the buffer with random bytes, blocking version.
pub fn blocking_fill_bytes(&mut self, destination: &mut [u8]) {
if destination.is_empty() {
return; // Nothing to fill
}
self.start_rng();
let mut buffer = [0u8; TRNG_BLOCK_SIZE_BYTES];
for chunk in destination.chunks_mut(TRNG_BLOCK_SIZE_BYTES) {
self.blocking_wait_for_successful_generation();
self.blocking_read_ehr_registers_into_array(&mut buffer);
chunk.copy_from_slice(&buffer[..chunk.len()])
}
self.stop_rng()
}
/// Return a random u32, blocking.
pub fn blocking_next_u32(&mut self) -> u32 {
let regs = T::regs();
self.start_rng();
self.blocking_wait_for_successful_generation();
// 12.12.3 After successful generation, read the last result register, EHR_DATA[5] to
// clear all of the result registers.
let result = regs.ehr_data5().read();
self.stop_rng();
result
}
/// Return a random u64, blocking.
pub fn blocking_next_u64(&mut self) -> u64 {
let regs = T::regs();
self.start_rng();
self.blocking_wait_for_successful_generation();
let low = regs.ehr_data4().read() as u64;
// 12.12.3 After successful generation, read the last result register, EHR_DATA[5] to
// clear all of the result registers.
let result = (regs.ehr_data5().read() as u64) << 32 | low;
self.stop_rng();
result
}
}
impl<'d, T: Instance> rand_core::RngCore for Trng<'d, T> {
fn next_u32(&mut self) -> u32 {
self.blocking_next_u32()
}
fn next_u64(&mut self) -> u64 {
self.blocking_next_u64()
}
fn fill_bytes(&mut self, dest: &mut [u8]) {
self.blocking_fill_bytes(dest)
}
fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Error> {
self.blocking_fill_bytes(dest);
Ok(())
}
}
/// TRNG interrupt handler.
pub struct InterruptHandler<T: Instance> {
_trng: PhantomData<T>,
}
impl<T: Instance> interrupt::typelevel::Handler<T::Interrupt> for InterruptHandler<T> {
unsafe fn on_interrupt() {
let regs = T::regs();
let isr = regs.rng_isr().read();
// Clear ehr bit
regs.rng_icr().write(|w| {
w.set_ehr_valid(true);
});
if isr.ehr_valid() {
T::waker().wake();
} else {
// 12.12.5. List of Registers
// ...
// TRNG: RNG_ISR Register
// ...
// AUTOCORR_ERR: 1 indicates Autocorrelation test failed four times in a row.
// When set, RNG ceases functioning until next reset
if isr.autocorr_err() {
warn!("TRNG Autocorrect error! Resetting TRNG");
regs.trng_sw_reset().write(|w| {
w.set_trng_sw_reset(true);
});
}
}
}
}

View File

@ -224,6 +224,17 @@ impl<'d, T: Instance, M: Mode> UartTx<'d, T, M> {
}
impl<'d, T: Instance> UartTx<'d, T, Blocking> {
/// Create a new UART TX instance for blocking mode operations.
pub fn new_blocking(
_uart: impl Peripheral<P = T> + 'd,
tx: impl Peripheral<P = impl TxPin<T>> + 'd,
config: Config,
) -> Self {
into_ref!(tx);
Uart::<T, Blocking>::init(Some(tx.map_into()), None, None, None, config);
Self::new_inner(None)
}
/// Convert this uart TX instance into a buffered uart using the provided
/// irq and transmit buffer.
pub fn into_buffered(

View File

@ -47,7 +47,7 @@ embassy-time = { version = "0.3.2", path = "../embassy-time", optional = true }
embassy-time-driver = { version = "0.1", path = "../embassy-time-driver", optional = true }
embassy-futures = { version = "0.1.0", path = "../embassy-futures" }
embassy-hal-internal = {version = "0.2.0", path = "../embassy-hal-internal", features = ["cortex-m", "prio-bits-4"] }
embassy-embedded-hal = {version = "0.2.0", path = "../embassy-embedded-hal" }
embassy-embedded-hal = {version = "0.2.0", path = "../embassy-embedded-hal", default-features = false }
embassy-net-driver = { version = "0.2.0", path = "../embassy-net-driver" }
embassy-usb-driver = {version = "0.1.0", path = "../embassy-usb-driver" }
embassy-usb-synopsys-otg = {version = "0.1.0", path = "../embassy-usb-synopsys-otg" }
@ -72,7 +72,7 @@ rand_core = "0.6.3"
sdio-host = "0.5.0"
critical-section = "1.1"
#stm32-metapac = { version = "15" }
stm32-metapac = { git = "https://github.com/embassy-rs/stm32-data-generated", tag = "stm32-data-5ef354f3e49f790e47f5c818f243459742c9b83b" }
stm32-metapac = { git = "https://github.com/embassy-rs/stm32-data-generated", tag = "stm32-data-9b7414490b10ffbd5beb1b0dcf14adb018cbe37f" }
vcell = "0.1.3"
nb = "1.0.0"
@ -88,6 +88,8 @@ static_assertions = { version = "1.1" }
volatile-register = { version = "0.2.1" }
bitflags = "2.4.2"
block-device-driver = { version = "0.2" }
aligned = "0.4.1"
[dev-dependencies]
critical-section = { version = "1.1", features = ["std"] }
@ -97,7 +99,7 @@ proc-macro2 = "1.0.36"
quote = "1.0.15"
#stm32-metapac = { version = "15", default-features = false, features = ["metadata"]}
stm32-metapac = { git = "https://github.com/embassy-rs/stm32-data-generated", tag = "stm32-data-5ef354f3e49f790e47f5c818f243459742c9b83b", default-features = false, features = ["metadata"] }
stm32-metapac = { git = "https://github.com/embassy-rs/stm32-data-generated", tag = "stm32-data-9b7414490b10ffbd5beb1b0dcf14adb018cbe37f", default-features = false, features = ["metadata"] }
[features]
default = ["rt"]
@ -127,7 +129,7 @@ unstable-pac = []
#! ## Time
## Enables additional driver features that depend on embassy-time
time = ["dep:embassy-time"]
time = ["dep:embassy-time", "embassy-embedded-hal/time"]
# Features starting with `_` are for internal use only. They're not intended
# to be enabled by other crates, and are not covered by semver guarantees.

View File

@ -55,7 +55,7 @@ fn main() {
let mut singletons: Vec<String> = Vec::new();
for p in METADATA.peripherals {
if let Some(r) = &p.registers {
if r.kind == "adccommon" || r.kind == "sai" || r.kind == "ucpd" {
if r.kind == "adccommon" || r.kind == "sai" || r.kind == "ucpd" || r.kind == "otg" {
// TODO: should we emit this for all peripherals? if so, we will need a list of all
// possible peripherals across all chips, so that we can declare the configs
// (replacing the hard-coded list of `peri_*` cfgs below)
@ -111,6 +111,8 @@ fn main() {
"peri_sai4",
"peri_ucpd1",
"peri_ucpd2",
"peri_usb_otg_fs",
"peri_usb_otg_hs",
]);
cfgs.declare_all(&["mco", "mco1", "mco2"]);
@ -1028,6 +1030,9 @@ fn main() {
(("hrtim", "CHE2"), quote!(crate::hrtim::ChannelEComplementaryPin)),
(("hrtim", "CHF1"), quote!(crate::hrtim::ChannelFPin)),
(("hrtim", "CHF2"), quote!(crate::hrtim::ChannelFComplementaryPin)),
(("lptim", "CH1"), quote!(crate::lptim::Channel1Pin)),
(("lptim", "CH2"), quote!(crate::lptim::Channel2Pin)),
(("lptim", "OUT"), quote!(crate::lptim::OutputPin)),
(("sdmmc", "CK"), quote!(crate::sdmmc::CkPin)),
(("sdmmc", "CMD"), quote!(crate::sdmmc::CmdPin)),
(("sdmmc", "D0"), quote!(crate::sdmmc::D0Pin)),

View File

@ -1,5 +1,9 @@
#[allow(unused)]
#[cfg(stm32h7)]
use pac::adc::vals::{Adcaldif, Difsel, Exten};
#[allow(unused)]
#[cfg(stm32g4)]
use pac::adc::vals::{Adcaldif, Difsel, Exten, Rovsm, Trovs};
use pac::adccommon::vals::Presc;
use super::{blocking_delay_us, Adc, AdcChannel, Instance, Resolution, SampleTime};
@ -228,6 +232,68 @@ impl<'d, T: Instance> Adc<'d, T> {
Vbat {}
}
/// Enable differential channel.
/// Caution:
/// : When configuring the channel “i” in differential input mode, its negative input voltage VINN[i]
/// is connected to another channel. As a consequence, this channel is no longer usable in
/// single-ended mode or in differential mode and must never be configured to be converted.
/// Some channels are shared between ADC1/ADC2/ADC3/ADC4/ADC5: this can make the
/// channel on the other ADC unusable. The only exception is when ADC master and the slave
/// operate in interleaved mode.
#[cfg(stm32g4)]
pub fn set_differential_channel(&mut self, ch: usize, enable: bool) {
T::regs().cr().modify(|w| w.set_aden(false)); // disable adc
T::regs().difsel().modify(|w| {
w.set_difsel(
ch,
if enable {
Difsel::DIFFERENTIAL
} else {
Difsel::SINGLEENDED
},
);
});
T::regs().cr().modify(|w| w.set_aden(true));
}
#[cfg(stm32g4)]
pub fn set_differential(&mut self, channel: &mut impl AdcChannel<T>, enable: bool) {
self.set_differential_channel(channel.channel() as usize, enable);
}
/// Set oversampling shift.
#[cfg(stm32g4)]
pub fn set_oversampling_shift(&mut self, shift: u8) {
T::regs().cfgr2().modify(|reg| reg.set_ovss(shift));
}
/// Set oversampling ratio.
#[cfg(stm32g4)]
pub fn set_oversampling_ratio(&mut self, ratio: u8) {
T::regs().cfgr2().modify(|reg| reg.set_ovsr(ratio));
}
/// Enable oversampling in regular mode.
#[cfg(stm32g4)]
pub fn enable_regular_oversampling_mode(&mut self, mode: Rovsm, trig_mode: Trovs, enable: bool) {
T::regs().cfgr2().modify(|reg| reg.set_trovs(trig_mode));
T::regs().cfgr2().modify(|reg| reg.set_rovsm(mode));
T::regs().cfgr2().modify(|reg| reg.set_rovse(enable));
}
// Reads that are not implemented as INJECTED in "blocking_read"
// #[cfg(stm32g4)]
// pub fn enalble_injected_oversampling_mode(&mut self, enable: bool) {
// T::regs().cfgr2().modify(|reg| reg.set_jovse(enable));
// }
// #[cfg(stm32g4)]
// pub fn enable_oversampling_regular_injected_mode(&mut self, enable: bool) {
// // the regularoversampling mode is forced to resumed mode (ROVSM bit ignored),
// T::regs().cfgr2().modify(|reg| reg.set_rovse(enable));
// T::regs().cfgr2().modify(|reg| reg.set_jovse(enable));
// }
/// Set the ADC sample time.
pub fn set_sample_time(&mut self, sample_time: SampleTime) {
self.sample_time = sample_time;

View File

@ -4,7 +4,7 @@
#![allow(missing_docs)] // TODO
#![cfg_attr(adc_f3_v2, allow(unused))]
#[cfg(not(adc_f3_v2))]
#[cfg(not(any(adc_f3_v2, adc_u5)))]
#[cfg_attr(adc_f1, path = "f1.rs")]
#[cfg_attr(adc_f3, path = "f3.rs")]
#[cfg_attr(adc_f3_v1_1, path = "f3_v1_1.rs")]
@ -19,13 +19,16 @@ mod _version;
use core::marker::PhantomData;
#[allow(unused)]
#[cfg(not(adc_f3_v2))]
#[cfg(not(any(adc_f3_v2, adc_u5)))]
pub use _version::*;
#[cfg(any(adc_f1, adc_f3, adc_v1, adc_l0, adc_f3_v1_1))]
use embassy_sync::waitqueue::AtomicWaker;
#[cfg(not(any(adc_f1, adc_f3_v2)))]
#[cfg(not(any(adc_u5)))]
pub use crate::pac::adc::vals;
#[cfg(not(any(adc_f1, adc_f3_v2, adc_u5)))]
pub use crate::pac::adc::vals::Res as Resolution;
#[cfg(not(any(adc_u5)))]
pub use crate::pac::adc::vals::SampleTime;
use crate::peripherals;
@ -35,7 +38,7 @@ dma_trait!(RxDma, Instance);
pub struct Adc<'d, T: Instance> {
#[allow(unused)]
adc: crate::PeripheralRef<'d, T>,
#[cfg(not(any(adc_f3_v2, adc_f3_v1_1)))]
#[cfg(not(any(adc_f3_v2, adc_f3_v1_1, adc_u5)))]
sample_time: SampleTime,
}
@ -56,7 +59,7 @@ impl State {
trait SealedInstance {
#[allow(unused)]
fn regs() -> crate::pac::adc::Adc;
#[cfg(not(any(adc_f1, adc_v1, adc_l0, adc_f3_v2, adc_f3_v1_1, adc_g0)))]
#[cfg(not(any(adc_f1, adc_v1, adc_l0, adc_f3_v2, adc_f3_v1_1, adc_g0, adc_u5)))]
#[allow(unused)]
fn common_regs() -> crate::pac::adccommon::AdcCommon;
#[cfg(any(adc_f1, adc_f3, adc_v1, adc_l0, adc_f3_v1_1))]
@ -162,7 +165,7 @@ foreach_adc!(
crate::pac::$inst
}
#[cfg(not(any(adc_f1, adc_v1, adc_l0, adc_f3_v2, adc_f3_v1_1, adc_g0)))]
#[cfg(not(any(adc_f1, adc_v1, adc_l0, adc_f3_v2, adc_f3_v1_1, adc_g0, adc_u5)))]
fn common_regs() -> crate::pac::adccommon::AdcCommon {
return crate::pac::$common_inst
}
@ -199,7 +202,7 @@ macro_rules! impl_adc_pin {
/// Get the maximum reading value for this resolution.
///
/// This is `2**n - 1`.
#[cfg(not(any(adc_f1, adc_f3_v2)))]
#[cfg(not(any(adc_f1, adc_f3_v2, adc_u5)))]
pub const fn resolution_to_max_count(res: Resolution) -> u32 {
match res {
#[cfg(adc_v4)]

View File

@ -68,7 +68,6 @@ pub struct SceInterruptHandler<T: Instance> {
impl<T: Instance> interrupt::typelevel::Handler<T::SCEInterrupt> for SceInterruptHandler<T> {
unsafe fn on_interrupt() {
info!("sce irq");
let msr = T::regs().msr();
let msr_val = msr.read();
@ -76,9 +75,8 @@ impl<T: Instance> interrupt::typelevel::Handler<T::SCEInterrupt> for SceInterrup
msr.modify(|m| m.set_slaki(true));
T::state().err_waker.wake();
} else if msr_val.erri() {
info!("Error interrupt");
// Disable the interrupt, but don't acknowledge the error, so that it can be
// forwarded off the the bus message consumer. If we don't provide some way for
// forwarded off the bus message consumer. If we don't provide some way for
// downstream code to determine that it has already provided this bus error instance
// to the bus message consumer, we are doomed to re-provide a single error instance for
// an indefinite amount of time.

View File

@ -200,7 +200,7 @@ impl Registers {
if header_reg.rtr().bit() {
F::new_remote(id, len as usize)
} else {
F::new(id, &data)
F::new(id, &data[0..(len as usize)])
}
} else {
// Abort request failed because the frame was already sent (or being sent) on

View File

@ -493,6 +493,26 @@ impl AnyChannel {
}
}
fn request_pause(&self) {
let info = self.info();
match self.info().dma {
#[cfg(dma)]
DmaInfo::Dma(r) => {
// Disable the channel without overwriting the existing configuration
r.st(info.num).cr().modify(|w| {
w.set_en(false);
});
}
#[cfg(bdma)]
DmaInfo::Bdma(r) => {
// Disable the channel without overwriting the existing configuration
r.ch(info.num).cr().modify(|w| {
w.set_en(false);
});
}
}
}
fn is_running(&self) -> bool {
let info = self.info();
match self.info().dma {
@ -667,12 +687,22 @@ impl<'a> Transfer<'a> {
}
/// Request the transfer to stop.
/// The configuration for this channel will **not be preserved**. If you need to restart the transfer
/// at a later point with the same configuration, see [`request_pause`](Self::request_pause) instead.
///
/// This doesn't immediately stop the transfer, you have to wait until [`is_running`](Self::is_running) returns false.
pub fn request_stop(&mut self) {
self.channel.request_stop()
}
/// Request the transfer to pause, keeping the existing configuration for this channel.
/// To restart the transfer, call [`start`](Self::start) again.
///
/// This doesn't immediately stop the transfer, you have to wait until [`is_running`](Self::is_running) returns false.
pub fn request_pause(&mut self) {
self.channel.request_pause()
}
/// Return whether this transfer is still running.
///
/// If this returns `false`, it can be because either the transfer finished, or
@ -777,6 +807,7 @@ impl<'a, W: Word> ReadableRingBuffer<'a, W> {
let dir = Dir::PeripheralToMemory;
let data_size = W::size();
options.half_transfer_ir = true;
options.complete_transfer_ir = true;
options.circular = true;
@ -845,13 +876,23 @@ impl<'a, W: Word> ReadableRingBuffer<'a, W> {
DmaCtrlImpl(self.channel.reborrow()).set_waker(waker);
}
/// Request DMA to stop.
/// Request the DMA to stop.
/// The configuration for this channel will **not be preserved**. If you need to restart the transfer
/// at a later point with the same configuration, see [`request_pause`](Self::request_pause) instead.
///
/// This doesn't immediately stop the transfer, you have to wait until [`is_running`](Self::is_running) returns false.
pub fn request_stop(&mut self) {
self.channel.request_stop()
}
/// Request the transfer to pause, keeping the existing configuration for this channel.
/// To restart the transfer, call [`start`](Self::start) again.
///
/// This doesn't immediately stop the transfer, you have to wait until [`is_running`](Self::is_running) returns false.
pub fn request_pause(&mut self) {
self.channel.request_pause()
}
/// Return whether DMA is still running.
///
/// If this returns `false`, it can be because either the transfer finished, or
@ -976,13 +1017,23 @@ impl<'a, W: Word> WritableRingBuffer<'a, W> {
DmaCtrlImpl(self.channel.reborrow()).set_waker(waker);
}
/// Request DMA to stop.
/// Request the DMA to stop.
/// The configuration for this channel will **not be preserved**. If you need to restart the transfer
/// at a later point with the same configuration, see [`request_pause`](Self::request_pause) instead.
///
/// This doesn't immediately stop the transfer, you have to wait until [`is_running`](Self::is_running) returns false.
pub fn request_stop(&mut self) {
self.channel.request_stop()
}
/// Request the transfer to pause, keeping the existing configuration for this channel.
/// To restart the transfer, call [`start`](Self::start) again.
///
/// This doesn't immediately stop the transfer, you have to wait until [`is_running`](Self::is_running) returns false.
pub fn request_pause(&mut self) {
self.channel.request_pause()
}
/// Return whether DMA is still running.
///
/// If this returns `false`, it can be because either the transfer finished, or

View File

@ -216,7 +216,10 @@ impl<'a> Transfer<'a> {
data_size: WordSize,
_options: TransferOptions,
) -> Self {
assert!(mem_len > 0 && mem_len <= 0xFFFF);
// BNDT is specified as bytes, not as number of transfers.
let Ok(bndt) = (mem_len * data_size.bytes()).try_into() else {
panic!("DMA transfers may not be larger than 65535 bytes.");
};
let info = channel.info();
let ch = info.dma.ch(info.num);
@ -226,9 +229,6 @@ impl<'a> Transfer<'a> {
let this = Self { channel };
#[cfg(dmamux)]
super::dmamux::configure_dmamux(&*this.channel, request);
ch.cr().write(|w| w.set_reset(true));
ch.fcr().write(|w| w.0 = 0xFFFF_FFFF); // clear all irqs
ch.llr().write(|_| {}); // no linked list
@ -245,10 +245,8 @@ impl<'a> Transfer<'a> {
});
w.set_reqsel(request);
});
ch.br1().write(|w| {
// BNDT is specified as bytes, not as number of transfers.
w.set_bndt((mem_len * data_size.bytes()) as u16)
});
ch.tr3().write(|_| {}); // no address offsets.
ch.br1().write(|w| w.set_bndt(bndt));
match dir {
Dir::MemoryToPeripheral => {

View File

@ -177,6 +177,20 @@ pub unsafe trait PHY {
fn poll_link<S: StationManagement>(&mut self, sm: &mut S, cx: &mut Context) -> bool;
}
impl<'d, T: Instance, P: PHY> Ethernet<'d, T, P> {
/// Directly expose the SMI interface used by the Ethernet driver.
///
/// This can be used to for example configure special PHY registers for compliance testing.
///
/// # Safety
///
/// Revert any temporary PHY register changes such as to enable test modes before handing
/// the Ethernet device over to the networking stack otherwise things likely won't work.
pub unsafe fn station_management(&mut self) -> &mut impl StationManagement {
&mut self.station_management
}
}
trait SealedInstance {
fn regs() -> crate::pac::eth::Eth;
}

View File

@ -0,0 +1,142 @@
use core::ptr::write_volatile;
use core::sync::atomic::{fence, AtomicBool, Ordering};
use pac::flash::regs::Sr;
use super::{FlashBank, FlashRegion, FlashSector, FLASH_REGIONS, WRITE_SIZE};
use crate::flash::Error;
use crate::pac;
static DATA_CACHE_WAS_ENABLED: AtomicBool = AtomicBool::new(false);
impl FlashSector {
const fn snb(&self) -> u8 {
((self.bank as u8) << 4) + self.index_in_bank
}
}
pub(crate) const fn is_default_layout() -> bool {
true
}
pub(crate) const fn get_flash_regions() -> &'static [&'static FlashRegion] {
&FLASH_REGIONS
}
pub(crate) unsafe fn lock() {
pac::FLASH.cr().modify(|w| w.set_lock(true));
}
pub(crate) unsafe fn unlock() {
if pac::FLASH.cr().read().lock() {
pac::FLASH.keyr().write_value(0x4567_0123);
pac::FLASH.keyr().write_value(0xCDEF_89AB);
}
}
pub(crate) unsafe fn enable_blocking_write() {
assert_eq!(0, WRITE_SIZE % 4);
save_data_cache_state();
pac::FLASH.cr().write(|w| {
w.set_pg(true);
w.set_psize(pac::flash::vals::Psize::PSIZE32);
});
}
pub(crate) unsafe fn disable_blocking_write() {
pac::FLASH.cr().write(|w| w.set_pg(false));
restore_data_cache_state();
}
pub(crate) unsafe fn blocking_write(start_address: u32, buf: &[u8; WRITE_SIZE]) -> Result<(), Error> {
write_start(start_address, buf);
blocking_wait_ready()
}
unsafe fn write_start(start_address: u32, buf: &[u8; WRITE_SIZE]) {
let mut address = start_address;
for val in buf.chunks(4) {
write_volatile(address as *mut u32, u32::from_le_bytes(unwrap!(val.try_into())));
address += val.len() as u32;
// prevents parallelism errors
fence(Ordering::SeqCst);
}
}
pub(crate) unsafe fn blocking_erase_sector(sector: &FlashSector) -> Result<(), Error> {
save_data_cache_state();
trace!("Blocking erasing sector number {}", sector.snb());
pac::FLASH.cr().modify(|w| {
w.set_ser(true);
w.set_snb(sector.snb())
});
pac::FLASH.cr().modify(|w| {
w.set_strt(true);
});
let ret: Result<(), Error> = blocking_wait_ready();
clear_all_err();
restore_data_cache_state();
ret
}
pub(crate) unsafe fn clear_all_err() {
// read and write back the same value.
// This clears all "write 1 to clear" bits.
pac::FLASH.sr().modify(|_| {});
}
unsafe fn blocking_wait_ready() -> Result<(), Error> {
loop {
let sr = pac::FLASH.sr().read();
if !sr.bsy() {
return get_result(sr);
}
}
}
fn get_result(sr: Sr) -> Result<(), Error> {
if sr.pgserr() {
Err(Error::Seq)
} else if sr.pgperr() {
Err(Error::Parallelism)
} else if sr.pgaerr() {
Err(Error::Unaligned)
} else if sr.wrperr() {
Err(Error::Protected)
} else {
Ok(())
}
}
fn save_data_cache_state() {
let dual_bank = unwrap!(get_flash_regions().last()).bank == FlashBank::Bank2;
if dual_bank {
// Disable data cache during write/erase if there are two banks, see errata 2.2.12
let dcen = pac::FLASH.acr().read().dcen();
DATA_CACHE_WAS_ENABLED.store(dcen, Ordering::Relaxed);
if dcen {
pac::FLASH.acr().modify(|w| w.set_dcen(false));
}
}
}
fn restore_data_cache_state() {
let dual_bank = unwrap!(get_flash_regions().last()).bank == FlashBank::Bank2;
if dual_bank {
// Restore data cache if it was enabled
let dcen = DATA_CACHE_WAS_ENABLED.load(Ordering::Relaxed);
if dcen {
// Reset data cache before we enable it again
pac::FLASH.acr().modify(|w| w.set_dcrst(true));
pac::FLASH.acr().modify(|w| w.set_dcrst(false));
pac::FLASH.acr().modify(|w| w.set_dcen(true))
}
}
}

View File

@ -94,6 +94,7 @@ pub enum FlashBank {
#[cfg_attr(any(flash_l0, flash_l1, flash_l4, flash_wl, flash_wb), path = "l.rs")]
#[cfg_attr(flash_f0, path = "f0.rs")]
#[cfg_attr(any(flash_f1, flash_f3), path = "f1f3.rs")]
#[cfg_attr(flash_f2, path = "f2.rs")]
#[cfg_attr(flash_f4, path = "f4.rs")]
#[cfg_attr(flash_f7, path = "f7.rs")]
#[cfg_attr(any(flash_g0, flash_g4c2, flash_g4c3, flash_g4c4), path = "g.rs")]
@ -104,8 +105,8 @@ pub enum FlashBank {
#[cfg_attr(flash_u0, path = "u0.rs")]
#[cfg_attr(
not(any(
flash_l0, flash_l1, flash_l4, flash_wl, flash_wb, flash_f0, flash_f1, flash_f3, flash_f4, flash_f7, flash_g0,
flash_g4c2, flash_g4c3, flash_g4c4, flash_h7, flash_h7ab, flash_u5, flash_h50, flash_u0
flash_l0, flash_l1, flash_l4, flash_wl, flash_wb, flash_f0, flash_f1, flash_f2, flash_f3, flash_f4, flash_f7,
flash_g0, flash_g4c2, flash_g4c3, flash_g4c4, flash_h7, flash_h7ab, flash_u5, flash_h50, flash_u0
)),
path = "other.rs"
)]

View File

@ -89,6 +89,8 @@ pub mod i2s;
pub mod ipcc;
#[cfg(feature = "low-power")]
pub mod low_power;
#[cfg(lptim)]
pub mod lptim;
#[cfg(ltdc)]
pub mod ltdc;
#[cfg(opamp)]

View File

@ -0,0 +1,18 @@
/// Timer channel.
#[derive(Clone, Copy)]
pub enum Channel {
/// Channel 1.
Ch1,
/// Channel 2.
Ch2,
}
impl Channel {
/// Get the channel index (0..1)
pub fn index(&self) -> usize {
match self {
Channel::Ch1 => 0,
Channel::Ch2 => 1,
}
}
}

View File

@ -0,0 +1,48 @@
//! Low-power timer (LPTIM)
pub mod pwm;
pub mod timer;
use crate::rcc::RccPeripheral;
/// Timer channel.
#[cfg(any(lptim_v2a, lptim_v2b))]
mod channel;
#[cfg(any(lptim_v2a, lptim_v2b))]
pub use channel::Channel;
pin_trait!(OutputPin, BasicInstance);
pin_trait!(Channel1Pin, BasicInstance);
pin_trait!(Channel2Pin, BasicInstance);
pub(crate) trait SealedInstance: RccPeripheral {
fn regs() -> crate::pac::lptim::Lptim;
}
pub(crate) trait SealedBasicInstance: RccPeripheral {}
/// LPTIM basic instance trait.
#[allow(private_bounds)]
pub trait BasicInstance: SealedBasicInstance + 'static {}
/// LPTIM instance trait.
#[allow(private_bounds)]
pub trait Instance: BasicInstance + SealedInstance + 'static {}
foreach_interrupt! {
($inst:ident, lptim, LPTIM, GLOBAL, $irq:ident) => {
impl SealedInstance for crate::peripherals::$inst {
fn regs() -> crate::pac::lptim::Lptim {
crate::pac::$inst
}
}
impl SealedBasicInstance for crate::peripherals::$inst {
}
impl BasicInstance for crate::peripherals::$inst {}
impl Instance for crate::peripherals::$inst {}
};
($inst:ident, lptim, LPTIM_BASIC, GLOBAL, $irq:ident) => {
impl SealedBasicInstance for crate::peripherals::$inst {
}
impl BasicInstance for crate::peripherals::$inst {}
};
}

View File

@ -0,0 +1,168 @@
//! PWM driver.
use core::marker::PhantomData;
use embassy_hal_internal::{into_ref, PeripheralRef};
use super::timer::Timer;
#[cfg(not(any(lptim_v2a, lptim_v2b)))]
use super::OutputPin;
#[cfg(any(lptim_v2a, lptim_v2b))]
use super::{channel::Channel, timer::ChannelDirection, Channel1Pin, Channel2Pin};
use super::{BasicInstance, Instance};
use crate::gpio::{AfType, AnyPin, OutputType, Speed};
use crate::time::Hertz;
use crate::Peripheral;
/// Output marker type.
pub enum Output {}
/// Channel 1 marker type.
pub enum Ch1 {}
/// Channel 2 marker type.
pub enum Ch2 {}
/// PWM pin wrapper.
///
/// This wraps a pin to make it usable with PWM.
pub struct PwmPin<'d, T, C> {
_pin: PeripheralRef<'d, AnyPin>,
phantom: PhantomData<(T, C)>,
}
macro_rules! channel_impl {
($new_chx:ident, $channel:ident, $pin_trait:ident) => {
impl<'d, T: BasicInstance> PwmPin<'d, T, $channel> {
#[doc = concat!("Create a new ", stringify!($channel), " PWM pin instance.")]
pub fn $new_chx(pin: impl Peripheral<P = impl $pin_trait<T>> + 'd) -> Self {
into_ref!(pin);
critical_section::with(|_| {
pin.set_low();
pin.set_as_af(
pin.af_num(),
AfType::output(OutputType::PushPull, Speed::VeryHigh),
);
});
PwmPin {
_pin: pin.map_into(),
phantom: PhantomData,
}
}
}
};
}
#[cfg(not(any(lptim_v2a, lptim_v2b)))]
channel_impl!(new, Output, OutputPin);
#[cfg(any(lptim_v2a, lptim_v2b))]
channel_impl!(new_ch1, Ch1, Channel1Pin);
#[cfg(any(lptim_v2a, lptim_v2b))]
channel_impl!(new_ch2, Ch2, Channel2Pin);
/// PWM driver.
pub struct Pwm<'d, T: Instance> {
inner: Timer<'d, T>,
}
#[cfg(not(any(lptim_v2a, lptim_v2b)))]
impl<'d, T: Instance> Pwm<'d, T> {
/// Create a new PWM driver.
pub fn new(tim: impl Peripheral<P = T> + 'd, _output_pin: PwmPin<'d, T, Output>, freq: Hertz) -> Self {
Self::new_inner(tim, freq)
}
/// Set the duty.
///
/// The value ranges from 0 for 0% duty, to [`get_max_duty`](Self::get_max_duty) for 100% duty, both included.
pub fn set_duty(&mut self, duty: u16) {
assert!(duty <= self.get_max_duty());
self.inner.set_compare_value(duty)
}
/// Get the duty.
///
/// The value ranges from 0 for 0% duty, to [`get_max_duty`](Self::get_max_duty) for 100% duty, both included.
pub fn get_duty(&self) -> u16 {
self.inner.get_compare_value()
}
fn post_init(&mut self) {}
}
#[cfg(any(lptim_v2a, lptim_v2b))]
impl<'d, T: Instance> Pwm<'d, T> {
/// Create a new PWM driver.
pub fn new(
tim: impl Peripheral<P = T> + 'd,
_ch1_pin: Option<PwmPin<'d, T, Ch1>>,
_ch2_pin: Option<PwmPin<'d, T, Ch2>>,
freq: Hertz,
) -> Self {
Self::new_inner(tim, freq)
}
/// Enable the given channel.
pub fn enable(&mut self, channel: Channel) {
self.inner.enable_channel(channel, true);
}
/// Disable the given channel.
pub fn disable(&mut self, channel: Channel) {
self.inner.enable_channel(channel, false);
}
/// Check whether given channel is enabled
pub fn is_enabled(&self, channel: Channel) -> bool {
self.inner.get_channel_enable_state(channel)
}
/// Set the duty for a given channel.
///
/// The value ranges from 0 for 0% duty, to [`get_max_duty`](Self::get_max_duty) for 100% duty, both included.
pub fn set_duty(&mut self, channel: Channel, duty: u16) {
assert!(duty <= self.get_max_duty());
self.inner.set_compare_value(channel, duty)
}
/// Get the duty for a given channel.
///
/// The value ranges from 0 for 0% duty, to [`get_max_duty`](Self::get_max_duty) for 100% duty, both included.
pub fn get_duty(&self, channel: Channel) -> u16 {
self.inner.get_compare_value(channel)
}
fn post_init(&mut self) {
[Channel::Ch1, Channel::Ch2].iter().for_each(|&channel| {
self.inner.set_channel_direction(channel, ChannelDirection::OutputPwm);
});
}
}
impl<'d, T: Instance> Pwm<'d, T> {
fn new_inner(tim: impl Peripheral<P = T> + 'd, freq: Hertz) -> Self {
let mut this = Self { inner: Timer::new(tim) };
this.inner.enable();
this.set_frequency(freq);
this.post_init();
this.inner.continuous_mode_start();
this
}
/// Set PWM frequency.
///
/// Note: when you call this, the max duty value changes, so you will have to
/// call `set_duty` on all channels with the duty calculated based on the new max duty.
pub fn set_frequency(&mut self, frequency: Hertz) {
self.inner.set_frequency(frequency);
}
/// Get max duty value.
///
/// This value depends on the configured frequency and the timer's clock rate from RCC.
pub fn get_max_duty(&self) -> u16 {
self.inner.get_max_compare_value() + 1
}
}

View File

@ -0,0 +1,18 @@
use crate::pac::lptim::vals;
/// Direction of a low-power timer channel
pub enum ChannelDirection {
/// Use channel as a PWM output
OutputPwm,
/// Use channel as an input capture
InputCapture,
}
impl From<ChannelDirection> for vals::Ccsel {
fn from(direction: ChannelDirection) -> Self {
match direction {
ChannelDirection::OutputPwm => vals::Ccsel::OUTPUTCOMPARE,
ChannelDirection::InputCapture => vals::Ccsel::INPUTCAPTURE,
}
}
}

View File

@ -0,0 +1,133 @@
//! Low-level timer driver.
mod prescaler;
use embassy_hal_internal::{into_ref, Peripheral, PeripheralRef};
#[cfg(any(lptim_v2a, lptim_v2b))]
use super::channel::Channel;
#[cfg(any(lptim_v2a, lptim_v2b))]
mod channel_direction;
#[cfg(any(lptim_v2a, lptim_v2b))]
pub use channel_direction::ChannelDirection;
use prescaler::Prescaler;
use super::Instance;
use crate::rcc;
use crate::time::Hertz;
/// Low-level timer driver.
pub struct Timer<'d, T: Instance> {
_tim: PeripheralRef<'d, T>,
}
impl<'d, T: Instance> Timer<'d, T> {
/// Create a new timer driver.
pub fn new(tim: impl Peripheral<P = T> + 'd) -> Self {
into_ref!(tim);
rcc::enable_and_reset::<T>();
Self { _tim: tim }
}
/// Enable the timer.
pub fn enable(&self) {
T::regs().cr().modify(|w| w.set_enable(true));
}
/// Disable the timer.
pub fn disable(&self) {
T::regs().cr().modify(|w| w.set_enable(false));
}
/// Start the timer in single pulse mode.
pub fn single_mode_start(&self) {
T::regs().cr().modify(|w| w.set_sngstrt(true));
}
/// Start the timer in continuous mode.
pub fn continuous_mode_start(&self) {
T::regs().cr().modify(|w| w.set_cntstrt(true));
}
/// Set the frequency of how many times per second the timer counts up to the max value or down to 0.
pub fn set_frequency(&self, frequency: Hertz) {
let f = frequency.0;
assert!(f > 0);
let pclk_f = T::frequency().0;
let pclk_ticks_per_timer_period = pclk_f / f;
let psc = Prescaler::from_ticks(pclk_ticks_per_timer_period);
let arr = psc.scale_down(pclk_ticks_per_timer_period);
T::regs().cfgr().modify(|r| r.set_presc((&psc).into()));
T::regs().arr().modify(|r| r.set_arr(arr.into()));
}
/// Get the timer frequency.
pub fn get_frequency(&self) -> Hertz {
let pclk_f = T::frequency();
let arr = T::regs().arr().read().arr();
let psc = Prescaler::from(T::regs().cfgr().read().presc());
pclk_f / psc.scale_up(arr)
}
/// Get the clock frequency of the timer (before prescaler is applied).
pub fn get_clock_frequency(&self) -> Hertz {
T::frequency()
}
/// Get max compare value. This depends on the timer frequency and the clock frequency from RCC.
pub fn get_max_compare_value(&self) -> u16 {
T::regs().arr().read().arr()
}
}
#[cfg(any(lptim_v2a, lptim_v2b))]
impl<'d, T: Instance> Timer<'d, T> {
/// Enable/disable a channel.
pub fn enable_channel(&self, channel: Channel, enable: bool) {
T::regs().ccmr(0).modify(|w| {
w.set_cce(channel.index(), enable);
});
}
/// Get enable/disable state of a channel
pub fn get_channel_enable_state(&self, channel: Channel) -> bool {
T::regs().ccmr(0).read().cce(channel.index())
}
/// Set compare value for a channel.
pub fn set_compare_value(&self, channel: Channel, value: u16) {
T::regs().ccr(channel.index()).modify(|w| w.set_ccr(value));
}
/// Get compare value for a channel.
pub fn get_compare_value(&self, channel: Channel) -> u16 {
T::regs().ccr(channel.index()).read().ccr()
}
/// Set channel direction.
#[cfg(any(lptim_v2a, lptim_v2b))]
pub fn set_channel_direction(&self, channel: Channel, direction: ChannelDirection) {
T::regs()
.ccmr(0)
.modify(|w| w.set_ccsel(channel.index(), direction.into()));
}
}
#[cfg(not(any(lptim_v2a, lptim_v2b)))]
impl<'d, T: Instance> Timer<'d, T> {
/// Set compare value for a channel.
pub fn set_compare_value(&self, value: u16) {
T::regs().cmp().modify(|w| w.set_cmp(value));
}
/// Get compare value for a channel.
pub fn get_compare_value(&self) -> u16 {
T::regs().cmp().read().cmp()
}
}

View File

@ -0,0 +1,90 @@
//! Low-level timer driver.
use crate::pac::lptim::vals;
pub enum Prescaler {
Div1,
Div2,
Div4,
Div8,
Div16,
Div32,
Div64,
Div128,
}
impl From<&Prescaler> for vals::Presc {
fn from(prescaler: &Prescaler) -> Self {
match prescaler {
Prescaler::Div1 => vals::Presc::DIV1,
Prescaler::Div2 => vals::Presc::DIV2,
Prescaler::Div4 => vals::Presc::DIV4,
Prescaler::Div8 => vals::Presc::DIV8,
Prescaler::Div16 => vals::Presc::DIV16,
Prescaler::Div32 => vals::Presc::DIV32,
Prescaler::Div64 => vals::Presc::DIV64,
Prescaler::Div128 => vals::Presc::DIV128,
}
}
}
impl From<vals::Presc> for Prescaler {
fn from(prescaler: vals::Presc) -> Self {
match prescaler {
vals::Presc::DIV1 => Prescaler::Div1,
vals::Presc::DIV2 => Prescaler::Div2,
vals::Presc::DIV4 => Prescaler::Div4,
vals::Presc::DIV8 => Prescaler::Div8,
vals::Presc::DIV16 => Prescaler::Div16,
vals::Presc::DIV32 => Prescaler::Div32,
vals::Presc::DIV64 => Prescaler::Div64,
vals::Presc::DIV128 => Prescaler::Div128,
}
}
}
impl From<&Prescaler> for u32 {
fn from(prescaler: &Prescaler) -> Self {
match prescaler {
Prescaler::Div1 => 1,
Prescaler::Div2 => 2,
Prescaler::Div4 => 4,
Prescaler::Div8 => 8,
Prescaler::Div16 => 16,
Prescaler::Div32 => 32,
Prescaler::Div64 => 64,
Prescaler::Div128 => 128,
}
}
}
impl From<u32> for Prescaler {
fn from(prescaler: u32) -> Self {
match prescaler {
1 => Prescaler::Div1,
2 => Prescaler::Div2,
4 => Prescaler::Div4,
8 => Prescaler::Div8,
16 => Prescaler::Div16,
32 => Prescaler::Div32,
64 => Prescaler::Div64,
128 => Prescaler::Div128,
_ => unreachable!(),
}
}
}
impl Prescaler {
pub fn from_ticks(ticks: u32) -> Self {
// We need to scale down to a 16-bit range
(ticks >> 16).next_power_of_two().into()
}
pub fn scale_down(&self, ticks: u32) -> u16 {
(ticks / u32::from(self)).try_into().unwrap()
}
pub fn scale_up(&self, ticks: u16) -> u32 {
u32::from(self) * ticks as u32
}
}

View File

@ -34,8 +34,10 @@ pub enum VoltageScale {
Scale2,
Scale3,
}
#[cfg(any(stm32h7rs))]
#[cfg(stm32h7rs)]
pub use crate::pac::pwr::vals::Vos as VoltageScale;
#[cfg(all(stm32h7rs, peri_usb_otg_hs))]
pub use crate::pac::rcc::vals::{Usbphycsel, Usbrefcksel};
#[derive(Clone, Copy, Eq, PartialEq)]
pub enum HseMode {
@ -557,6 +559,27 @@ pub(crate) unsafe fn init(config: Config) {
let rtc = config.ls.init();
#[cfg(all(stm32h7rs, peri_usb_otg_hs))]
let usb_refck = match config.mux.usbphycsel {
Usbphycsel::HSE => hse,
Usbphycsel::HSE_DIV_2 => hse.map(|hse_val| hse_val / 2u8),
Usbphycsel::PLL3_Q => pll3.q,
_ => None,
};
#[cfg(all(stm32h7rs, peri_usb_otg_hs))]
let usb_refck_sel = match usb_refck {
Some(clk_val) => match clk_val {
Hertz(16_000_000) => Usbrefcksel::MHZ16,
Hertz(19_200_000) => Usbrefcksel::MHZ19_2,
Hertz(20_000_000) => Usbrefcksel::MHZ20,
Hertz(24_000_000) => Usbrefcksel::MHZ24,
Hertz(26_000_000) => Usbrefcksel::MHZ26,
Hertz(32_000_000) => Usbrefcksel::MHZ32,
_ => panic!("cannot select USBPHYC reference clock with source frequency of {} Hz, must be one of 16, 19.2, 20, 24, 26, 32 MHz", clk_val),
},
None => Usbrefcksel::MHZ24,
};
#[cfg(stm32h7)]
{
RCC.d1cfgr().modify(|w| {
@ -593,6 +616,11 @@ pub(crate) unsafe fn init(config: Config) {
w.set_ppre4(config.apb4_pre);
w.set_ppre5(config.apb5_pre);
});
#[cfg(peri_usb_otg_hs)]
RCC.ahbperckselr().modify(|w| {
w.set_usbrefcksel(usb_refck_sel);
});
}
#[cfg(stm32h5)]
{
@ -698,7 +726,9 @@ pub(crate) unsafe fn init(config: Config) {
#[cfg(stm32h7rs)]
clk48mohci: None, // TODO
#[cfg(stm32h7rs)]
usb: None, // TODO
hse_div_2: hse.map(|clk| clk / 2u32),
#[cfg(stm32h7rs)]
usb: Some(Hertz(48_000_000)),
);
}
@ -769,7 +799,7 @@ fn init_pll(num: usize, config: Option<Pll>, input: &PllInput) -> PllOutput {
if num == 0 {
// on PLL1, DIVP must be even for most series.
// The enum value is 1 less than the divider, so check it's odd.
#[cfg(not(pwr_h7rm0468))]
#[cfg(not(any(pwr_h7rm0468, stm32h7rs)))]
assert!(div.to_bits() % 2 == 1);
#[cfg(pwr_h7rm0468)]
assert!(div.to_bits() % 2 == 1 || div.to_bits() == 0);

View File

@ -661,12 +661,12 @@ fn get_af_types(mode: Mode, tx_rx: TxRx) -> (AfType, AfType) {
//sd is defined by tx/rx mode
match tx_rx {
TxRx::Transmitter => AfType::output(OutputType::PushPull, Speed::VeryHigh),
TxRx::Receiver => AfType::input(Pull::None),
TxRx::Receiver => AfType::input(Pull::Down), // Ensure mute level when no input is connected.
},
//clocks (mclk, sck and fs) are defined by master/slave
match mode {
Mode::Master => AfType::output(OutputType::PushPull, Speed::VeryHigh),
Mode::Slave => AfType::input(Pull::None),
Mode::Slave => AfType::input(Pull::Down), // Ensure no clocks when no input is connected.
},
)
}
@ -987,6 +987,21 @@ impl<'d, T: Instance, W: word::Word> Sai<'d, T, W> {
ch.cr2().modify(|w| w.set_mute(value));
}
/// Determine the mute state of the receiver.
///
/// Clears the mute state flag in the status register.
pub fn is_muted(&self) -> Result<bool, Error> {
match &self.ring_buffer {
RingBuffer::Readable(_) => {
let ch = T::REGS.ch(self.sub_block as usize);
let mute_state = ch.sr().read().mutedet();
ch.clrfr().write(|w| w.set_cmutedet(true));
Ok(mute_state)
}
_ => Err(Error::NotAReceiver),
}
}
/// Write data to the SAI ringbuffer.
///
/// This appends the data to the buffer and returns immediately. The

View File

@ -1511,3 +1511,44 @@ foreach_peripheral!(
}
};
);
impl<'d, T: Instance, Dma: SdmmcDma<T> + 'd> block_device_driver::BlockDevice<512> for Sdmmc<'d, T, Dma> {
type Error = Error;
type Align = aligned::A4;
async fn read(
&mut self,
mut block_address: u32,
buf: &mut [aligned::Aligned<Self::Align, [u8; 512]>],
) -> Result<(), Self::Error> {
// FIXME/TODO because of missing read_blocks multiple we have to do this one block at a time
for block in buf.iter_mut() {
// safety aligned by block device
let block = unsafe { &mut *(block as *mut _ as *mut crate::sdmmc::DataBlock) };
self.read_block(block_address, block).await?;
block_address += 1;
}
Ok(())
}
async fn write(
&mut self,
mut block_address: u32,
buf: &[aligned::Aligned<Self::Align, [u8; 512]>],
) -> Result<(), Self::Error> {
// FIXME/TODO because of missing read_blocks multiple we have to do this one block at a time
for block in buf.iter() {
// safety aligned by block device
let block = unsafe { &*(block as *const _ as *const crate::sdmmc::DataBlock) };
self.write_block(block_address, block).await?;
block_address += 1;
}
Ok(())
}
async fn size(&mut self) -> Result<u64, Self::Error> {
Ok(self.card()?.size())
}
}

View File

@ -311,51 +311,29 @@ impl<'d, M: PeriMode> Spi<'d, M> {
}
}
/// Set SPI word size. Disables SPI if needed, you have to enable it back yourself.
fn set_word_size(&mut self, word_size: word_impl::Config) {
if self.current_word_size == word_size {
return;
}
self.info.regs.cr1().modify(|w| {
w.set_spe(false);
});
#[cfg(any(spi_v1, spi_f1))]
{
self.info.regs.cr1().modify(|reg| {
reg.set_spe(false);
reg.set_dff(word_size)
});
self.info.regs.cr1().modify(|reg| {
reg.set_spe(true);
});
}
self.info.regs.cr1().modify(|reg| {
reg.set_dff(word_size);
});
#[cfg(spi_v2)]
{
self.info.regs.cr1().modify(|w| {
w.set_spe(false);
});
self.info.regs.cr2().modify(|w| {
w.set_frxth(word_size.1);
w.set_ds(word_size.0);
});
self.info.regs.cr1().modify(|w| {
w.set_spe(true);
});
}
self.info.regs.cr2().modify(|w| {
w.set_frxth(word_size.1);
w.set_ds(word_size.0);
});
#[cfg(any(spi_v3, spi_v4, spi_v5))]
{
self.info.regs.cr1().modify(|w| {
w.set_csusp(true);
});
while self.info.regs.sr().read().eot() {}
self.info.regs.cr1().modify(|w| {
w.set_spe(false);
});
self.info.regs.cfg1().modify(|w| {
w.set_dsize(word_size);
});
self.info.regs.cr1().modify(|w| {
w.set_csusp(false);
w.set_spe(true);
});
}
self.info.regs.cfg1().modify(|w| {
w.set_dsize(word_size);
});
self.current_word_size = word_size;
}
@ -365,9 +343,9 @@ impl<'d, M: PeriMode> Spi<'d, M> {
// needed in v3+ to avoid overrun causing the SPI RX state machine to get stuck...?
#[cfg(any(spi_v3, spi_v4, spi_v5))]
self.info.regs.cr1().modify(|w| w.set_spe(false));
self.set_word_size(W::CONFIG);
self.info.regs.cr1().modify(|w| w.set_spe(true));
flush_rx_fifo(self.info.regs);
self.set_word_size(W::CONFIG);
for word in words.iter() {
// this cannot use `transfer_word` because on SPIv2 and higher,
// the SPI RX state machine hangs if no physical pin is connected to the SCK AF.
@ -402,9 +380,9 @@ impl<'d, M: PeriMode> Spi<'d, M> {
// needed in v3+ to avoid overrun causing the SPI RX state machine to get stuck...?
#[cfg(any(spi_v3, spi_v4, spi_v5))]
self.info.regs.cr1().modify(|w| w.set_spe(false));
self.set_word_size(W::CONFIG);
self.info.regs.cr1().modify(|w| w.set_spe(true));
flush_rx_fifo(self.info.regs);
self.set_word_size(W::CONFIG);
for word in words.iter_mut() {
*word = transfer_word(self.info.regs, W::default())?;
}
@ -418,9 +396,9 @@ impl<'d, M: PeriMode> Spi<'d, M> {
// needed in v3+ to avoid overrun causing the SPI RX state machine to get stuck...?
#[cfg(any(spi_v3, spi_v4, spi_v5))]
self.info.regs.cr1().modify(|w| w.set_spe(false));
self.set_word_size(W::CONFIG);
self.info.regs.cr1().modify(|w| w.set_spe(true));
flush_rx_fifo(self.info.regs);
self.set_word_size(W::CONFIG);
for word in words.iter_mut() {
*word = transfer_word(self.info.regs, *word)?;
}
@ -437,9 +415,9 @@ impl<'d, M: PeriMode> Spi<'d, M> {
// needed in v3+ to avoid overrun causing the SPI RX state machine to get stuck...?
#[cfg(any(spi_v3, spi_v4, spi_v5))]
self.info.regs.cr1().modify(|w| w.set_spe(false));
self.set_word_size(W::CONFIG);
self.info.regs.cr1().modify(|w| w.set_spe(true));
flush_rx_fifo(self.info.regs);
self.set_word_size(W::CONFIG);
let len = read.len().max(write.len());
for i in 0..len {
let wb = write.get(i).copied().unwrap_or_default();
@ -648,10 +626,10 @@ impl<'d> Spi<'d, Async> {
return Ok(());
}
self.set_word_size(W::CONFIG);
self.info.regs.cr1().modify(|w| {
w.set_spe(false);
});
self.set_word_size(W::CONFIG);
let tx_dst = self.info.regs.tx_ptr();
let tx_f = unsafe { self.tx_dma.as_mut().unwrap().write(data, tx_dst, Default::default()) };
@ -685,6 +663,8 @@ impl<'d> Spi<'d, Async> {
w.set_spe(false);
});
self.set_word_size(W::CONFIG);
let comm = regs.cfg2().modify(|w| {
let prev = w.comm();
w.set_comm(vals::Comm::RECEIVER);
@ -707,7 +687,6 @@ impl<'d> Spi<'d, Async> {
let rx_src = regs.rx_ptr();
for mut chunk in data.chunks_mut(u16::max_value().into()) {
self.set_word_size(W::CONFIG);
set_rxdmaen(regs, true);
let tsize = chunk.len();
@ -765,12 +744,12 @@ impl<'d> Spi<'d, Async> {
return Ok(());
}
self.set_word_size(W::CONFIG);
self.info.regs.cr1().modify(|w| {
w.set_spe(false);
});
self.set_word_size(W::CONFIG);
// SPIv3 clears rxfifo on SPE=0
#[cfg(not(any(spi_v3, spi_v4, spi_v5)))]
flush_rx_fifo(self.info.regs);
@ -783,7 +762,7 @@ impl<'d> Spi<'d, Async> {
let rx_f = unsafe { self.rx_dma.as_mut().unwrap().read(rx_src, data, Default::default()) };
let tx_dst = self.info.regs.tx_ptr();
let clock_byte = 0x00u8;
let clock_byte = W::default();
let tx_f = unsafe {
self.tx_dma
.as_mut()
@ -813,11 +792,12 @@ impl<'d> Spi<'d, Async> {
return Ok(());
}
self.set_word_size(W::CONFIG);
self.info.regs.cr1().modify(|w| {
w.set_spe(false);
});
self.set_word_size(W::CONFIG);
// SPIv3 clears rxfifo on SPE=0
#[cfg(not(any(spi_v3, spi_v4, spi_v5)))]
flush_rx_fifo(self.info.regs);
@ -1195,7 +1175,7 @@ trait SealedWord {
/// Word sizes usable for SPI.
#[allow(private_bounds)]
pub trait Word: word::Word + SealedWord {}
pub trait Word: word::Word + SealedWord + Default {}
macro_rules! impl_word {
($T:ty, $config:expr) => {

View File

@ -27,7 +27,7 @@ use crate::dma::{ChannelAndRequest, TransferOptions};
use crate::interrupt;
use crate::interrupt::typelevel::Interrupt;
use crate::pac::ucpd::vals::{Anamode, Ccenable, PscUsbpdclk, Txmode};
pub use crate::pac::ucpd::vals::{Phyccsel as CcSel, TypecVstateCc as CcVState};
pub use crate::pac::ucpd::vals::{Phyccsel as CcSel, Rxordset, TypecVstateCc as CcVState};
use crate::rcc::{self, RccPeripheral};
pub(crate) fn init(
@ -86,6 +86,34 @@ pub enum CcPull {
Source3_0A,
}
/// UCPD configuration
#[non_exhaustive]
#[derive(Copy, Clone, Debug)]
pub struct Config {
/// Receive SOP packets
pub sop: bool,
/// Receive SOP' packets
pub sop_prime: bool,
/// Receive SOP'' packets
pub sop_double_prime: bool,
/// Receive SOP'_Debug packets
pub sop_prime_debug: bool,
/// Receive SOP''_Debug packets
pub sop_double_prime_debug: bool,
}
impl Default for Config {
fn default() -> Self {
Self {
sop: true,
sop_prime: false,
sop_double_prime: false,
sop_prime_debug: false,
sop_double_prime_debug: false,
}
}
}
/// UCPD driver.
pub struct Ucpd<'d, T: Instance> {
cc_phy: CcPhy<'d, T>,
@ -98,6 +126,7 @@ impl<'d, T: Instance> Ucpd<'d, T> {
_irq: impl interrupt::typelevel::Binding<T::Interrupt, InterruptHandler<T>> + 'd,
cc1: impl Peripheral<P = impl Cc1Pin<T>> + 'd,
cc2: impl Peripheral<P = impl Cc2Pin<T>> + 'd,
config: Config,
) -> Self {
into_ref!(cc1, cc2);
cc1.set_as_analog();
@ -129,9 +158,15 @@ impl<'d, T: Instance> Ucpd<'d, T> {
// 1.75us * 17 = ~30us
w.set_ifrgap(17 - 1);
// TODO: Currently only hard reset and SOP messages can be received.
// UNDOCUMENTED: This register can only be written while UCPDEN=0 (found by testing).
w.set_rxordseten(0b1001);
let rxordset = (config.sop as u16) << 0
| (config.sop_prime as u16) << 1
| (config.sop_double_prime as u16) << 2
// Hard reset
| 0x1 << 3
| (config.sop_prime_debug as u16) << 4
| (config.sop_double_prime_debug as u16) << 5;
w.set_rxordseten(rxordset);
// Enable DMA
w.set_txdmaen(true);
@ -288,6 +323,22 @@ impl<'d, T: Instance> CcPhy<'d, T> {
}
}
/// Receive SOP.
#[derive(Debug, Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum Sop {
/// SOP
Sop,
/// SOP'
SopPrime,
/// SOP''
SopDoublePrime,
/// SOP'_Debug
SopPrimeDebug,
/// SOP''_Debug
SopDoublePrimeDebug,
}
/// Receive Error.
#[derive(Debug, Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
@ -340,6 +391,13 @@ impl<'d, T: Instance> PdPhy<'d, T> {
///
/// Returns the number of received bytes or an error.
pub async fn receive(&mut self, buf: &mut [u8]) -> Result<usize, RxError> {
self.receive_with_sop(buf).await.map(|(_sop, size)| size)
}
/// Receives SOP and a PD message into the provided buffer.
///
/// Returns the start of packet type and number of received bytes or an error.
pub async fn receive_with_sop(&mut self, buf: &mut [u8]) -> Result<(Sop, usize), RxError> {
let r = T::REGS;
let dma = unsafe {
@ -388,7 +446,18 @@ impl<'d, T: Instance> PdPhy<'d, T> {
}
}
Ok(r.rx_payszr().read().rxpaysz().into())
let sop = match r.rx_ordsetr().read().rxordset() {
Rxordset::SOP => Sop::Sop,
Rxordset::SOPPRIME => Sop::SopPrime,
Rxordset::SOPDOUBLEPRIME => Sop::SopDoublePrime,
Rxordset::SOPPRIMEDEBUG => Sop::SopPrimeDebug,
Rxordset::SOPDOUBLEPRIMEDEBUG => Sop::SopDoublePrimeDebug,
Rxordset::CABLERESET => return Err(RxError::HardReset),
// Extension headers are not supported
_ => unreachable!(),
};
Ok((sop, r.rx_payszr().read().rxpaysz().into()))
}
fn enable_rx_interrupt(enable: bool) {

View File

@ -12,8 +12,8 @@ use embassy_sync::waitqueue::AtomicWaker;
#[cfg(not(any(usart_v1, usart_v2)))]
use super::DePin;
use super::{
clear_interrupt_flags, configure, rdr, reconfigure, sr, tdr, Config, ConfigError, CtsPin, Error, Info, Instance,
Regs, RtsPin, RxPin, TxPin,
clear_interrupt_flags, configure, rdr, reconfigure, send_break, sr, tdr, Config, ConfigError, CtsPin, Error, Info,
Instance, Regs, RtsPin, RxPin, TxPin,
};
use crate::gpio::{AfType, AnyPin, OutputType, Pull, SealedPin as _, Speed};
use crate::interrupt::{self, InterruptExt};
@ -359,6 +359,11 @@ impl<'d> BufferedUart<'d> {
Ok(())
}
/// Send break character
pub fn send_break(&self) {
self.tx.send_break()
}
}
impl<'d> BufferedUartRx<'d> {
@ -538,6 +543,11 @@ impl<'d> BufferedUartTx<'d> {
Ok(())
}
/// Send break character
pub fn send_break(&self) {
send_break(&self.info.regs);
}
}
impl<'d> Drop for BufferedUartRx<'d> {

View File

@ -14,7 +14,7 @@ use embassy_sync::waitqueue::AtomicWaker;
use futures_util::future::{select, Either};
use crate::dma::ChannelAndRequest;
use crate::gpio::{AfType, AnyPin, OutputType, Pull, SealedPin as _, Speed};
use crate::gpio::{self, AfType, AnyPin, OutputType, Pull, SealedPin as _, Speed};
use crate::interrupt::typelevel::Interrupt as _;
use crate::interrupt::{self, Interrupt, InterruptExt};
use crate::mode::{Async, Blocking, Mode};
@ -211,6 +211,30 @@ impl Default for Config {
}
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
/// Half duplex IO mode
pub enum HalfDuplexConfig {
/// Push pull allows for faster baudrates, may require series resistor
PushPull,
/// Open drain output using external pull up resistor
OpenDrainExternal,
#[cfg(not(gpio_v1))]
/// Open drain output using internal pull up resistor
OpenDrainInternal,
}
impl HalfDuplexConfig {
fn af_type(self) -> gpio::AfType {
match self {
HalfDuplexConfig::PushPull => AfType::output(OutputType::PushPull, Speed::Medium),
HalfDuplexConfig::OpenDrainExternal => AfType::output(OutputType::OpenDrain, Speed::Medium),
#[cfg(not(gpio_v1))]
HalfDuplexConfig::OpenDrainInternal => AfType::output_pull(OutputType::OpenDrain, Speed::Medium, Pull::Up),
}
}
}
/// Serial error
#[derive(Debug, Eq, PartialEq, Copy, Clone)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
@ -496,6 +520,11 @@ impl<'d, M: Mode> UartTx<'d, M> {
pub fn blocking_flush(&mut self) -> Result<(), Error> {
blocking_flush(self.info)
}
/// Send break character
pub fn send_break(&self) {
send_break(&self.info.regs);
}
}
fn blocking_flush(info: &Info) -> Result<(), Error> {
@ -510,6 +539,21 @@ fn blocking_flush(info: &Info) -> Result<(), Error> {
Ok(())
}
/// Send break character
pub fn send_break(regs: &Regs) {
// Busy wait until previous break has been sent
#[cfg(any(usart_v1, usart_v2))]
while regs.cr1().read().sbk() {}
#[cfg(any(usart_v3, usart_v4))]
while regs.isr().read().sbkf() {}
// Send break right after completing the current character transmission
#[cfg(any(usart_v1, usart_v2))]
regs.cr1().modify(|w| w.set_sbk(true));
#[cfg(any(usart_v3, usart_v4))]
regs.rqr().write(|w| w.set_sbkrq(true));
}
impl<'d> UartRx<'d, Async> {
/// Create a new rx-only UART with no hardware flow control.
///
@ -1029,8 +1073,9 @@ impl<'d> Uart<'d, Async> {
/// (when it is available for your chip). There is no functional difference between these methods, as both
/// allow bidirectional communication.
///
/// The pin is always released when no data is transmitted. Thus, it acts as a standard
/// I/O in idle or in reception.
/// The TX pin is always released when no data is transmitted. Thus, it acts as a standard
/// I/O in idle or in reception. It means that the I/O must be configured so that TX is
/// configured as alternate function open-drain with an external pull-up
/// Apart from this, the communication protocol is similar to normal USART mode. Any conflict
/// on the line must be managed by software (for instance by using a centralized arbiter).
#[doc(alias("HDSEL"))]
@ -1041,6 +1086,7 @@ impl<'d> Uart<'d, Async> {
tx_dma: impl Peripheral<P = impl TxDma<T>> + 'd,
rx_dma: impl Peripheral<P = impl RxDma<T>> + 'd,
mut config: Config,
half_duplex: HalfDuplexConfig,
) -> Result<Self, ConfigError> {
#[cfg(not(any(usart_v1, usart_v2)))]
{
@ -1051,7 +1097,7 @@ impl<'d> Uart<'d, Async> {
Self::new_inner(
peri,
None,
new_pin!(tx, AfType::output(OutputType::PushPull, Speed::Medium)),
new_pin!(tx, half_duplex.af_type()),
None,
None,
None,
@ -1079,6 +1125,7 @@ impl<'d> Uart<'d, Async> {
tx_dma: impl Peripheral<P = impl TxDma<T>> + 'd,
rx_dma: impl Peripheral<P = impl RxDma<T>> + 'd,
mut config: Config,
half_duplex: HalfDuplexConfig,
) -> Result<Self, ConfigError> {
config.swap_rx_tx = true;
config.half_duplex = true;
@ -1087,7 +1134,7 @@ impl<'d> Uart<'d, Async> {
peri,
None,
None,
new_pin!(rx, AfType::output(OutputType::PushPull, Speed::Medium)),
new_pin!(rx, half_duplex.af_type()),
None,
None,
new_dma!(tx_dma),
@ -1192,6 +1239,7 @@ impl<'d> Uart<'d, Blocking> {
peri: impl Peripheral<P = T> + 'd,
tx: impl Peripheral<P = impl TxPin<T>> + 'd,
mut config: Config,
half_duplex: HalfDuplexConfig,
) -> Result<Self, ConfigError> {
#[cfg(not(any(usart_v1, usart_v2)))]
{
@ -1202,7 +1250,7 @@ impl<'d> Uart<'d, Blocking> {
Self::new_inner(
peri,
None,
new_pin!(tx, AfType::output(OutputType::PushPull, Speed::Medium)),
new_pin!(tx, half_duplex.af_type()),
None,
None,
None,
@ -1227,6 +1275,7 @@ impl<'d> Uart<'d, Blocking> {
peri: impl Peripheral<P = T> + 'd,
rx: impl Peripheral<P = impl RxPin<T>> + 'd,
mut config: Config,
half_duplex: HalfDuplexConfig,
) -> Result<Self, ConfigError> {
config.swap_rx_tx = true;
config.half_duplex = true;
@ -1235,7 +1284,7 @@ impl<'d> Uart<'d, Blocking> {
peri,
None,
None,
new_pin!(rx, AfType::output(OutputType::PushPull, Speed::Medium)),
new_pin!(rx, half_duplex.af_type()),
None,
None,
None,
@ -1336,6 +1385,11 @@ impl<'d, M: Mode> Uart<'d, M> {
pub fn split(self) -> (UartTx<'d, M>, UartRx<'d, M>) {
(self.tx, self.rx)
}
/// Send break character
pub fn send_break(&self) {
self.tx.send_break();
}
}
fn reconfigure(info: &Info, kernel_clock: Hertz, config: &Config) -> Result<(), ConfigError> {

View File

@ -71,34 +71,19 @@ impl<'d> UartRx<'d, Async> {
}
impl<'d> RingBufferedUartRx<'d> {
/// Clear the ring buffer and start receiving in the background
pub fn start(&mut self) -> Result<(), Error> {
// Clear the ring buffer so that it is ready to receive data
self.ring_buf.clear();
self.setup_uart();
Ok(())
}
fn stop(&mut self, err: Error) -> Result<usize, Error> {
self.teardown_uart();
Err(err)
}
/// Reconfigure the driver
pub fn set_config(&mut self, config: &Config) -> Result<(), ConfigError> {
reconfigure(self.info, self.kernel_clock, config)
}
/// Start uart background receive
fn setup_uart(&mut self) {
// fence before starting DMA.
/// Configure and start the DMA backed UART receiver
///
/// Note: This is also done automatically by [`read()`] if required.
pub fn start_uart(&mut self) {
// Clear the buffer so that it is ready to receive data
compiler_fence(Ordering::SeqCst);
// start the dma controller
self.ring_buf.start();
self.ring_buf.clear();
let r = self.info.regs;
// clear all interrupts and DMA Rx Request
@ -118,9 +103,9 @@ impl<'d> RingBufferedUartRx<'d> {
});
}
/// Stop uart background receive
fn teardown_uart(&mut self) {
self.ring_buf.request_stop();
/// Stop DMA backed UART receiver
fn stop_uart(&mut self) {
self.ring_buf.request_pause();
let r = self.info.regs;
// clear all interrupts and DMA Rx Request
@ -153,13 +138,15 @@ impl<'d> RingBufferedUartRx<'d> {
pub async fn read(&mut self, buf: &mut [u8]) -> Result<usize, Error> {
let r = self.info.regs;
// Start background receive if it was not already started
// Start DMA and Uart if it was not already started,
// otherwise check for errors in status register.
let sr = clear_idle_flag(r);
if !r.cr3().read().dmar() {
self.start()?;
self.start_uart();
} else {
check_for_errors(sr)?;
}
check_for_errors(clear_idle_flag(r))?;
loop {
match self.ring_buf.read(buf) {
Ok((0, _)) => {}
@ -167,14 +154,16 @@ impl<'d> RingBufferedUartRx<'d> {
return Ok(len);
}
Err(_) => {
return self.stop(Error::Overrun);
self.stop_uart();
return Err(Error::Overrun);
}
}
match self.wait_for_data_or_idle().await {
Ok(_) => {}
Err(err) => {
return self.stop(err);
self.stop_uart();
return Err(err);
}
}
}
@ -184,20 +173,6 @@ impl<'d> RingBufferedUartRx<'d> {
async fn wait_for_data_or_idle(&mut self) -> Result<(), Error> {
compiler_fence(Ordering::SeqCst);
let mut dma_init = false;
// Future which completes when there is dma is half full or full
let dma = poll_fn(|cx| {
self.ring_buf.set_waker(cx.waker());
let status = match dma_init {
false => Poll::Pending,
true => Poll::Ready(()),
};
dma_init = true;
status
});
// Future which completes when idle line is detected
let s = self.state;
let uart = poll_fn(|cx| {
@ -219,16 +194,30 @@ impl<'d> RingBufferedUartRx<'d> {
}
});
match select(dma, uart).await {
Either::Left(((), _)) => Ok(()),
Either::Right((result, _)) => result,
let mut dma_init = false;
// Future which completes when there is dma is half full or full
let dma = poll_fn(|cx| {
self.ring_buf.set_waker(cx.waker());
let status = match dma_init {
false => Poll::Pending,
true => Poll::Ready(()),
};
dma_init = true;
status
});
match select(uart, dma).await {
Either::Left((result, _)) => result,
Either::Right(((), _)) => Ok(()),
}
}
}
impl Drop for RingBufferedUartRx<'_> {
fn drop(&mut self) {
self.teardown_uart();
self.stop_uart();
self.rx.as_ref().map(|x| x.set_as_disconnected());
self.rts.as_ref().map(|x| x.set_as_disconnected());
super::drop_tx_rx(self.info, self.state);

View File

@ -13,9 +13,19 @@ fn common_init<T: Instance>() {
// Check the USB clock is enabled and running at exactly 48 MHz.
// frequency() will panic if not enabled
let freq = T::frequency();
// On the H7RS, the USBPHYC embeds a PLL accepting one of the input frequencies listed below and providing 48MHz to OTG_FS and 60MHz to OTG_HS internally
#[cfg(stm32h7rs)]
if ![16_000_000, 19_200_000, 20_000_000, 24_000_000, 26_000_000, 32_000_000].contains(&freq.0) {
panic!(
"USB clock should be one of 16, 19.2, 20, 24, 26, 32Mhz but is {} Hz. Please double-check your RCC settings.",
freq.0
)
}
// Check frequency is within the 0.25% tolerance allowed by the spec.
// Clock might not be exact 48Mhz due to rounding errors in PLL calculation, or if the user
// has tight clock restrictions due to something else (like audio).
#[cfg(not(stm32h7rs))]
if freq.0.abs_diff(48_000_000) > 120_000 {
panic!(
"USB clock should be 48Mhz but is {} Hz. Please double-check your RCC settings.",
@ -48,6 +58,26 @@ fn common_init<T: Instance>() {
while !crate::pac::PWR.cr3().read().usb33rdy() {}
}
#[cfg(stm32h7rs)]
{
// If true, VDD33USB is generated by internal regulator from VDD50USB
// If false, VDD33USB and VDD50USB must be suplied directly with 3.3V (default on nucleo)
// TODO: unhardcode
let internal_regulator = false;
// Enable USB power
critical_section::with(|_| {
crate::pac::PWR.csr2().modify(|w| {
w.set_usbregen(internal_regulator);
w.set_usb33den(true);
w.set_usbhsregen(true);
})
});
// Wait for USB power to stabilize
while !crate::pac::PWR.csr2().read().usb33rdy() {}
}
#[cfg(stm32u5)]
{
// Enable USB power

View File

@ -97,6 +97,94 @@ impl<'d, T: Instance> Driver<'d, T> {
}
}
/// Initializes USB OTG peripheral with internal High-Speed PHY.
///
/// # Arguments
///
/// * `ep_out_buffer` - An internal buffer used to temporarily store received packets.
/// Must be large enough to fit all OUT endpoint max packet sizes.
/// Endpoint allocation will fail if it is too small.
pub fn new_hs(
_peri: impl Peripheral<P = T> + 'd,
_irq: impl interrupt::typelevel::Binding<T::Interrupt, InterruptHandler<T>> + 'd,
dp: impl Peripheral<P = impl DpPin<T>> + 'd,
dm: impl Peripheral<P = impl DmPin<T>> + 'd,
ep_out_buffer: &'d mut [u8],
config: Config,
) -> Self {
into_ref!(dp, dm);
dp.set_as_af(dp.af_num(), AfType::output(OutputType::PushPull, Speed::VeryHigh));
dm.set_as_af(dm.af_num(), AfType::output(OutputType::PushPull, Speed::VeryHigh));
let regs = T::regs();
let instance = OtgInstance {
regs,
state: T::state(),
fifo_depth_words: T::FIFO_DEPTH_WORDS,
extra_rx_fifo_words: RX_FIFO_EXTRA_SIZE_WORDS,
endpoint_count: T::ENDPOINT_COUNT,
phy_type: PhyType::InternalHighSpeed,
quirk_setup_late_cnak: quirk_setup_late_cnak(regs),
calculate_trdt_fn: calculate_trdt::<T>,
};
Self {
inner: OtgDriver::new(ep_out_buffer, instance, config),
phantom: PhantomData,
}
}
/// Initializes USB OTG peripheral with external Full-speed PHY (usually, a High-speed PHY in Full-speed mode).
///
/// # Arguments
///
/// * `ep_out_buffer` - An internal buffer used to temporarily store received packets.
/// Must be large enough to fit all OUT endpoint max packet sizes.
/// Endpoint allocation will fail if it is too small.
pub fn new_fs_ulpi(
_peri: impl Peripheral<P = T> + 'd,
_irq: impl interrupt::typelevel::Binding<T::Interrupt, InterruptHandler<T>> + 'd,
ulpi_clk: impl Peripheral<P = impl UlpiClkPin<T>> + 'd,
ulpi_dir: impl Peripheral<P = impl UlpiDirPin<T>> + 'd,
ulpi_nxt: impl Peripheral<P = impl UlpiNxtPin<T>> + 'd,
ulpi_stp: impl Peripheral<P = impl UlpiStpPin<T>> + 'd,
ulpi_d0: impl Peripheral<P = impl UlpiD0Pin<T>> + 'd,
ulpi_d1: impl Peripheral<P = impl UlpiD1Pin<T>> + 'd,
ulpi_d2: impl Peripheral<P = impl UlpiD2Pin<T>> + 'd,
ulpi_d3: impl Peripheral<P = impl UlpiD3Pin<T>> + 'd,
ulpi_d4: impl Peripheral<P = impl UlpiD4Pin<T>> + 'd,
ulpi_d5: impl Peripheral<P = impl UlpiD5Pin<T>> + 'd,
ulpi_d6: impl Peripheral<P = impl UlpiD6Pin<T>> + 'd,
ulpi_d7: impl Peripheral<P = impl UlpiD7Pin<T>> + 'd,
ep_out_buffer: &'d mut [u8],
config: Config,
) -> Self {
config_ulpi_pins!(
ulpi_clk, ulpi_dir, ulpi_nxt, ulpi_stp, ulpi_d0, ulpi_d1, ulpi_d2, ulpi_d3, ulpi_d4, ulpi_d5, ulpi_d6,
ulpi_d7
);
let regs = T::regs();
let instance = OtgInstance {
regs: T::regs(),
state: T::state(),
fifo_depth_words: T::FIFO_DEPTH_WORDS,
extra_rx_fifo_words: RX_FIFO_EXTRA_SIZE_WORDS,
endpoint_count: T::ENDPOINT_COUNT,
phy_type: PhyType::ExternalFullSpeed,
quirk_setup_late_cnak: quirk_setup_late_cnak(regs),
calculate_trdt_fn: calculate_trdt::<T>,
};
Self {
inner: OtgDriver::new(ep_out_buffer, instance, config),
phantom: PhantomData,
}
}
/// Initializes USB OTG peripheral with external High-Speed PHY.
///
/// # Arguments
@ -223,6 +311,19 @@ impl<'d, T: Instance> Bus<'d, T> {
}
});
#[cfg(stm32h7rs)]
critical_section::with(|_| {
let rcc = crate::pac::RCC;
rcc.ahb1enr().modify(|w| {
w.set_usbphycen(true);
w.set_usb_otg_hsen(true);
});
rcc.ahb1lpenr().modify(|w| {
w.set_usbphyclpen(true);
w.set_usb_otg_hslpen(true);
});
});
let r = T::regs();
let core_id = r.cid().read().0;
trace!("Core id {:08x}", core_id);
@ -237,6 +338,7 @@ impl<'d, T: Instance> Bus<'d, T> {
match core_id {
0x0000_1200 | 0x0000_1100 => self.inner.config_v1(),
0x0000_2000 | 0x0000_2100 | 0x0000_2300 | 0x0000_3000 | 0x0000_3100 => self.inner.config_v2v3(),
0x0000_5000 => self.inner.config_v5(),
_ => unimplemented!("Unknown USB core id {:X}", core_id),
}
}
@ -452,7 +554,7 @@ fn calculate_trdt<T: Instance>(speed: Dspd) -> u8 {
match speed {
Dspd::HIGH_SPEED => {
// From RM0431 (F72xx), RM0090 (F429), RM0390 (F446)
if ahb_freq >= 30_000_000 {
if ahb_freq >= 30_000_000 || cfg!(stm32h7rs) {
0x9
} else {
panic!("AHB frequency is too low")

View File

@ -80,6 +80,8 @@ impl<T: Instance> interrupt::typelevel::Handler<T::Interrupt> for InterruptHandl
if istr.ctr() {
let index = istr.ep_id() as usize;
CTR_TRIGGERED[index].store(true, Ordering::Relaxed);
let mut epr = regs.epr(index).read();
if epr.ctr_rx() {
if index == 0 && epr.setup() {
@ -120,6 +122,10 @@ const USBRAM_ALIGN: usize = 4;
const NEW_AW: AtomicWaker = AtomicWaker::new();
static BUS_WAKER: AtomicWaker = NEW_AW;
static EP0_SETUP: AtomicBool = AtomicBool::new(false);
const NEW_CTR_TRIGGERED: AtomicBool = AtomicBool::new(false);
static CTR_TRIGGERED: [AtomicBool; EP_COUNT] = [NEW_CTR_TRIGGERED; EP_COUNT];
static EP_IN_WAKERS: [AtomicWaker; EP_COUNT] = [NEW_AW; EP_COUNT];
static EP_OUT_WAKERS: [AtomicWaker; EP_COUNT] = [NEW_AW; EP_COUNT];
static IRQ_RESET: AtomicBool = AtomicBool::new(false);
@ -163,20 +169,37 @@ fn calc_out_len(len: u16) -> (u16, u16) {
mod btable {
use super::*;
pub(super) fn write_in<T: Instance>(index: usize, addr: u16) {
pub(super) fn write_in_tx<T: Instance>(index: usize, addr: u16) {
USBRAM.mem(index * 4 + 0).write_value(addr);
}
pub(super) fn write_in_len<T: Instance>(index: usize, _addr: u16, len: u16) {
pub(super) fn write_in_rx<T: Instance>(index: usize, addr: u16) {
USBRAM.mem(index * 4 + 2).write_value(addr);
}
pub(super) fn write_in_len_rx<T: Instance>(index: usize, _addr: u16, len: u16) {
USBRAM.mem(index * 4 + 3).write_value(len);
}
pub(super) fn write_in_len_tx<T: Instance>(index: usize, _addr: u16, len: u16) {
USBRAM.mem(index * 4 + 1).write_value(len);
}
pub(super) fn write_out<T: Instance>(index: usize, addr: u16, max_len_bits: u16) {
pub(super) fn write_out_rx<T: Instance>(index: usize, addr: u16, max_len_bits: u16) {
USBRAM.mem(index * 4 + 2).write_value(addr);
USBRAM.mem(index * 4 + 3).write_value(max_len_bits);
}
pub(super) fn read_out_len<T: Instance>(index: usize) -> u16 {
pub(super) fn write_out_tx<T: Instance>(index: usize, addr: u16, max_len_bits: u16) {
USBRAM.mem(index * 4 + 0).write_value(addr);
USBRAM.mem(index * 4 + 1).write_value(max_len_bits);
}
pub(super) fn read_out_len_tx<T: Instance>(index: usize) -> u16 {
USBRAM.mem(index * 4 + 1).read()
}
pub(super) fn read_out_len_rx<T: Instance>(index: usize) -> u16 {
USBRAM.mem(index * 4 + 3).read()
}
}
@ -184,19 +207,37 @@ mod btable {
mod btable {
use super::*;
pub(super) fn write_in<T: Instance>(_index: usize, _addr: u16) {}
pub(super) fn write_in_tx<T: Instance>(_index: usize, _addr: u16) {}
pub(super) fn write_in_len<T: Instance>(index: usize, addr: u16, len: u16) {
pub(super) fn write_in_rx<T: Instance>(_index: usize, _addr: u16) {}
pub(super) fn write_in_len_tx<T: Instance>(index: usize, addr: u16, len: u16) {
USBRAM.mem(index * 2).write_value((addr as u32) | ((len as u32) << 16));
}
pub(super) fn write_out<T: Instance>(index: usize, addr: u16, max_len_bits: u16) {
pub(super) fn write_in_len_rx<T: Instance>(index: usize, addr: u16, len: u16) {
USBRAM
.mem(index * 2 + 1)
.write_value((addr as u32) | ((len as u32) << 16));
}
pub(super) fn write_out_tx<T: Instance>(index: usize, addr: u16, max_len_bits: u16) {
USBRAM
.mem(index * 2)
.write_value((addr as u32) | ((max_len_bits as u32) << 16));
}
pub(super) fn write_out_rx<T: Instance>(index: usize, addr: u16, max_len_bits: u16) {
USBRAM
.mem(index * 2 + 1)
.write_value((addr as u32) | ((max_len_bits as u32) << 16));
}
pub(super) fn read_out_len<T: Instance>(index: usize) -> u16 {
pub(super) fn read_out_len_tx<T: Instance>(index: usize) -> u16 {
(USBRAM.mem(index * 2).read() >> 16) as u16
}
pub(super) fn read_out_len_rx<T: Instance>(index: usize) -> u16 {
(USBRAM.mem(index * 2 + 1).read() >> 16) as u16
}
}
@ -327,6 +368,13 @@ impl<'d, T: Instance> Driver<'d, T> {
return false; // reserved for control pipe
}
let used = ep.used_out || ep.used_in;
if used && (ep.ep_type == EndpointType::Isochronous || ep.ep_type == EndpointType::Bulk) {
// Isochronous and bulk endpoints are double-buffered.
// Their corresponding endpoint/channel registers are forced to be unidirectional.
// Do not reuse this index.
return false;
}
let used_dir = match D::dir() {
Direction::Out => ep.used_out,
Direction::In => ep.used_in,
@ -350,7 +398,11 @@ impl<'d, T: Instance> Driver<'d, T> {
let addr = self.alloc_ep_mem(len);
trace!(" len_bits = {:04x}", len_bits);
btable::write_out::<T>(index, addr, len_bits);
btable::write_out_rx::<T>(index, addr, len_bits);
if ep_type == EndpointType::Isochronous {
btable::write_out_tx::<T>(index, addr, len_bits);
}
EndpointBuffer {
addr,
@ -366,7 +418,11 @@ impl<'d, T: Instance> Driver<'d, T> {
let addr = self.alloc_ep_mem(len);
// ep_in_len is written when actually TXing packets.
btable::write_in::<T>(index, addr);
btable::write_in_tx::<T>(index, addr);
if ep_type == EndpointType::Isochronous {
btable::write_in_rx::<T>(index, addr);
}
EndpointBuffer {
addr,
@ -656,6 +712,18 @@ impl Dir for Out {
}
}
/// Selects the packet buffer.
///
/// For double-buffered endpoints, both the `Rx` and `Tx` buffer from a channel are used for the same
/// direction of transfer. This is opposed to single-buffered endpoints, where one channel can serve
/// two directions at the same time.
enum PacketBuffer {
/// The RX buffer - must be used for single-buffered OUT endpoints
Rx,
/// The TX buffer - must be used for single-buffered IN endpoints
Tx,
}
/// USB endpoint.
pub struct Endpoint<'d, T: Instance, D> {
_phantom: PhantomData<(&'d mut T, D)>,
@ -664,15 +732,46 @@ pub struct Endpoint<'d, T: Instance, D> {
}
impl<'d, T: Instance, D> Endpoint<'d, T, D> {
fn write_data(&mut self, buf: &[u8]) {
/// Write to a double-buffered endpoint.
///
/// For double-buffered endpoints, the data buffers overlap, but we still need to write to the right counter field.
/// The DTOG_TX bit indicates the buffer that is currently in use by the USB peripheral, that is, the buffer in
/// which the next transmit packet will be stored, so we need to write the counter of the OTHER buffer, which is
/// where the last transmitted packet was stored.
fn write_data_double_buffered(&mut self, buf: &[u8], packet_buffer: PacketBuffer) {
let index = self.info.addr.index();
self.buf.write(buf);
btable::write_in_len::<T>(index, self.buf.addr, buf.len() as _);
match packet_buffer {
PacketBuffer::Rx => btable::write_in_len_rx::<T>(index, self.buf.addr, buf.len() as _),
PacketBuffer::Tx => btable::write_in_len_tx::<T>(index, self.buf.addr, buf.len() as _),
}
}
fn read_data(&mut self, buf: &mut [u8]) -> Result<usize, EndpointError> {
/// Write to a single-buffered endpoint.
fn write_data(&mut self, buf: &[u8]) {
self.write_data_double_buffered(buf, PacketBuffer::Tx);
}
/// Read from a double-buffered endpoint.
///
/// For double-buffered endpoints, the data buffers overlap, but we still need to read from the right counter field.
/// The DTOG_RX bit indicates the buffer that is currently in use by the USB peripheral, that is, the buffer in
/// which the next received packet will be stored, so we need to read the counter of the OTHER buffer, which is
/// where the last received packet was stored.
fn read_data_double_buffered(
&mut self,
buf: &mut [u8],
packet_buffer: PacketBuffer,
) -> Result<usize, EndpointError> {
let index = self.info.addr.index();
let rx_len = btable::read_out_len::<T>(index) as usize & 0x3FF;
let rx_len = match packet_buffer {
PacketBuffer::Rx => btable::read_out_len_rx::<T>(index),
PacketBuffer::Tx => btable::read_out_len_tx::<T>(index),
} as usize
& 0x3FF;
trace!("READ DONE, rx_len = {}", rx_len);
if rx_len > buf.len() {
return Err(EndpointError::BufferOverflow);
@ -680,6 +779,11 @@ impl<'d, T: Instance, D> Endpoint<'d, T, D> {
self.buf.read(&mut buf[..rx_len]);
Ok(rx_len)
}
/// Read from a single-buffered endpoint.
fn read_data(&mut self, buf: &mut [u8]) -> Result<usize, EndpointError> {
self.read_data_double_buffered(buf, PacketBuffer::Rx)
}
}
impl<'d, T: Instance> driver::Endpoint for Endpoint<'d, T, In> {
@ -734,25 +838,53 @@ impl<'d, T: Instance> driver::EndpointOut for Endpoint<'d, T, Out> {
EP_OUT_WAKERS[index].register(cx.waker());
let regs = T::regs();
let stat = regs.epr(index).read().stat_rx();
if matches!(stat, Stat::NAK | Stat::DISABLED) {
Poll::Ready(stat)
if self.info.ep_type == EndpointType::Isochronous {
// The isochronous endpoint does not change its `STAT_RX` field to `NAK` when receiving a packet.
// Therefore, this instead waits until the `CTR` interrupt was triggered.
if matches!(stat, Stat::DISABLED) || CTR_TRIGGERED[index].load(Ordering::Relaxed) {
Poll::Ready(stat)
} else {
Poll::Pending
}
} else {
Poll::Pending
if matches!(stat, Stat::NAK | Stat::DISABLED) {
Poll::Ready(stat)
} else {
Poll::Pending
}
}
})
.await;
CTR_TRIGGERED[index].store(false, Ordering::Relaxed);
if stat == Stat::DISABLED {
return Err(EndpointError::Disabled);
}
let rx_len = self.read_data(buf)?;
let regs = T::regs();
let packet_buffer = if self.info.ep_type == EndpointType::Isochronous {
// Find the buffer, which is currently in use. Read from the OTHER buffer.
if regs.epr(index).read().dtog_rx() {
PacketBuffer::Rx
} else {
PacketBuffer::Tx
}
} else {
PacketBuffer::Rx
};
let rx_len = self.read_data_double_buffered(buf, packet_buffer)?;
regs.epr(index).write(|w| {
w.set_ep_type(convert_type(self.info.ep_type));
w.set_ea(self.info.addr.index() as _);
w.set_stat_rx(Stat::from_bits(Stat::NAK.to_bits() ^ Stat::VALID.to_bits()));
if self.info.ep_type == EndpointType::Isochronous {
w.set_stat_rx(Stat::from_bits(0)); // STAT_RX remains `VALID`.
} else {
w.set_stat_rx(Stat::from_bits(Stat::NAK.to_bits() ^ Stat::VALID.to_bits()));
}
w.set_stat_tx(Stat::from_bits(0));
w.set_ctr_rx(true); // don't clear
w.set_ctr_tx(true); // don't clear
@ -776,25 +908,54 @@ impl<'d, T: Instance> driver::EndpointIn for Endpoint<'d, T, In> {
EP_IN_WAKERS[index].register(cx.waker());
let regs = T::regs();
let stat = regs.epr(index).read().stat_tx();
if matches!(stat, Stat::NAK | Stat::DISABLED) {
Poll::Ready(stat)
if self.info.ep_type == EndpointType::Isochronous {
// The isochronous endpoint does not change its `STAT_RX` field to `NAK` when receiving a packet.
// Therefore, this instead waits until the `CTR` interrupt was triggered.
if matches!(stat, Stat::DISABLED) || CTR_TRIGGERED[index].load(Ordering::Relaxed) {
Poll::Ready(stat)
} else {
Poll::Pending
}
} else {
Poll::Pending
if matches!(stat, Stat::NAK | Stat::DISABLED) {
Poll::Ready(stat)
} else {
Poll::Pending
}
}
})
.await;
CTR_TRIGGERED[index].store(false, Ordering::Relaxed);
if stat == Stat::DISABLED {
return Err(EndpointError::Disabled);
}
self.write_data(buf);
let regs = T::regs();
let packet_buffer = if self.info.ep_type == EndpointType::Isochronous {
// Find the buffer, which is currently in use. Write to the OTHER buffer.
if regs.epr(index).read().dtog_tx() {
PacketBuffer::Tx
} else {
PacketBuffer::Rx
}
} else {
PacketBuffer::Tx
};
self.write_data_double_buffered(buf, packet_buffer);
let regs = T::regs();
regs.epr(index).write(|w| {
w.set_ep_type(convert_type(self.info.ep_type));
w.set_ea(self.info.addr.index() as _);
w.set_stat_tx(Stat::from_bits(Stat::NAK.to_bits() ^ Stat::VALID.to_bits()));
if self.info.ep_type == EndpointType::Isochronous {
w.set_stat_tx(Stat::from_bits(0)); // STAT_TX remains `VALID`.
} else {
w.set_stat_tx(Stat::from_bits(Stat::NAK.to_bits() ^ Stat::VALID.to_bits()));
}
w.set_stat_rx(Stat::from_bits(0));
w.set_ctr_rx(true); // don't clear
w.set_ctr_tx(true); // don't clear

View File

@ -194,6 +194,25 @@ impl<M: RawMutex, T: Clone, const CAP: usize, const SUBS: usize, const PUBS: usi
}
}
impl<M: RawMutex, T: Clone, const CAP: usize, const SUBS: usize, const PUBS: usize> crate::pubsub::PubSubBehavior<T>
for PubSubChannel<M, T, CAP, SUBS, PUBS>
{
fn publish_immediate(&self, message: T) {
self.inner.lock(|s| {
let mut s = s.borrow_mut();
s.publish_immediate(message)
})
}
fn capacity(&self) -> usize {
self.capacity()
}
fn is_full(&self) -> bool {
self.is_full()
}
}
impl<M: RawMutex, T: Clone, const CAP: usize, const SUBS: usize, const PUBS: usize> SealedPubSubBehavior<T>
for PubSubChannel<M, T, CAP, SUBS, PUBS>
{
@ -246,13 +265,6 @@ impl<M: RawMutex, T: Clone, const CAP: usize, const SUBS: usize, const PUBS: usi
})
}
fn publish_immediate(&self, message: T) {
self.inner.lock(|s| {
let mut s = s.borrow_mut();
s.publish_immediate(message)
})
}
fn unregister_subscriber(&self, subscriber_next_message_id: u64) {
self.inner.lock(|s| {
let mut s = s.borrow_mut();
@ -267,10 +279,6 @@ impl<M: RawMutex, T: Clone, const CAP: usize, const SUBS: usize, const PUBS: usi
})
}
fn capacity(&self) -> usize {
self.capacity()
}
fn free_capacity(&self) -> usize {
self.free_capacity()
}
@ -286,10 +294,6 @@ impl<M: RawMutex, T: Clone, const CAP: usize, const SUBS: usize, const PUBS: usi
fn is_empty(&self) -> bool {
self.is_empty()
}
fn is_full(&self) -> bool {
self.is_full()
}
}
/// Internal state for the PubSub channel
@ -445,8 +449,6 @@ pub enum Error {
MaximumPublishersReached,
}
/// 'Middle level' behaviour of the pubsub channel.
/// This trait is used so that Sub and Pub can be generic over the channel.
trait SealedPubSubBehavior<T> {
/// Try to get a message from the queue with the given message id.
///
@ -462,12 +464,6 @@ trait SealedPubSubBehavior<T> {
/// If the queue is full and a context is given, then its waker is registered in the publisher wakers.
fn publish_with_context(&self, message: T, cx: Option<&mut Context<'_>>) -> Result<(), T>;
/// Publish a message immediately
fn publish_immediate(&self, message: T);
/// Returns the maximum number of elements the channel can hold.
fn capacity(&self) -> usize;
/// Returns the free capacity of the channel.
///
/// This is equivalent to `capacity() - len()`
@ -482,9 +478,6 @@ trait SealedPubSubBehavior<T> {
/// Returns whether the channel is empty.
fn is_empty(&self) -> bool;
/// Returns whether the channel is full.
fn is_full(&self) -> bool;
/// Let the channel know that a subscriber has dropped
fn unregister_subscriber(&self, subscriber_next_message_id: u64);
@ -495,9 +488,16 @@ trait SealedPubSubBehavior<T> {
/// 'Middle level' behaviour of the pubsub channel.
/// This trait is used so that Sub and Pub can be generic over the channel.
#[allow(private_bounds)]
pub trait PubSubBehavior<T>: SealedPubSubBehavior<T> {}
pub trait PubSubBehavior<T>: SealedPubSubBehavior<T> {
/// Publish a message immediately
fn publish_immediate(&self, message: T);
impl<T, C: SealedPubSubBehavior<T>> PubSubBehavior<T> for C {}
/// Returns the maximum number of elements the channel can hold.
fn capacity(&self) -> usize;
/// Returns whether the channel is full.
fn is_full(&self) -> bool;
}
/// The result of the subscriber wait procedure
#[derive(Debug, Clone, PartialEq, Eq)]

View File

@ -98,7 +98,7 @@ pub trait Driver: Send + Sync + 'static {
///
/// Implementations MUST ensure that:
/// - This is guaranteed to be monotonic, i.e. a call to now() will always return
/// a greater or equal value than earler calls. Time can't "roll backwards".
/// a greater or equal value than earlier calls. Time can't "roll backwards".
/// - It "never" overflows. It must not overflow in a sufficiently long time frame, say
/// in 10_000 years (Human civilization is likely to already have self-destructed
/// 10_000 years from now.). This means if your hardware only has 16bit/32bit timers
@ -113,24 +113,52 @@ pub trait Driver: Send + Sync + 'static {
/// It is UB to make the alarm fire before setting a callback.
unsafe fn allocate_alarm(&self) -> Option<AlarmHandle>;
/// Sets the callback function to be called when the alarm triggers.
/// Set the callback function to be called when the alarm triggers.
/// The callback may be called from any context (interrupt or thread mode).
fn set_alarm_callback(&self, alarm: AlarmHandle, callback: fn(*mut ()), ctx: *mut ());
/// Sets an alarm at the given timestamp. When the current timestamp reaches the alarm
/// timestamp, the provided callback function will be called.
/// Set an alarm at the given timestamp.
///
/// The `Driver` implementation should guarantee that the alarm callback is never called synchronously from `set_alarm`.
/// Rather - if `timestamp` is already in the past - `false` should be returned and alarm should not be set,
/// or alternatively, the driver should return `true` and arrange to call the alarm callback as soon as possible, but not synchronously.
/// There is a rare third possibility that the alarm was barely in the future, and by the time it was enabled, it had slipped into the
/// past. This is can be detected by double-checking that the alarm is still in the future after enabling it; if it isn't, `false`
/// should also be returned to indicate that the callback may have been called already by the alarm, but it is not guaranteed, so the
/// caller should also call the callback, just like in the more common `false` case. (Note: This requires idempotency of the callback.)
/// ## Behavior
///
/// When callback is called, it is guaranteed that now() will return a value greater or equal than timestamp.
/// If `timestamp` is in the future, `set_alarm` schedules calling the callback function
/// at that time, and returns `true`.
///
/// Only one alarm can be active at a time for each AlarmHandle. This overwrites any previously-set alarm if any.
/// If `timestamp` is in the past, `set_alarm` has two allowed behaviors. Implementations can pick whether to:
///
/// - Schedule calling the callback function "immediately", as if the requested timestamp was "now+epsilon" and return `true`, or
/// - Not schedule the callback, and return `false`.
///
/// Callers must ensure to behave correctly with either behavior.
///
/// When callback is called, it is guaranteed that `now()` will return a value greater than or equal to `timestamp`.
///
/// ## Reentrancy
///
/// Calling the callback from `set_alarm` synchronously is not allowed. If the implementation chooses the first option above,
/// it must still call the callback from another context (i.e. an interrupt handler or background thread), it's not allowed
/// to call it synchronously in the context `set_alarm` is running.
///
/// The reason for the above is callers are explicitly permitted to do both of:
/// - Lock a mutex in the alarm callback.
/// - Call `set_alarm` while having that mutex locked.
///
/// If `set_alarm` called the callback synchronously, it'd cause a deadlock or panic because it'd cause the
/// mutex to be locked twice reentrantly in the same context.
///
/// ## Overwriting alarms
///
/// Only one alarm can be active at a time for each `AlarmHandle`. This overwrites any previously-set alarm if any.
///
/// ## Unsetting the alarm
///
/// There is no `unset_alarm` API. Instead, callers can call `set_alarm` with `timestamp` set to `u64::MAX`.
///
/// This allows for more efficient implementations, since they don't need to distinguish between the "alarm set" and
/// "alarm not set" cases, thanks to the fact "Alarm set for u64::MAX" is effectively equivalent for "alarm not set".
///
/// This means implementations need to be careful to avoid timestamp overflows. The recommendation is to make `timestamp`
/// be in the same units as hardware ticks to avoid any conversions, which makes avoiding overflow easier.
fn set_alarm(&self, alarm: AlarmHandle, timestamp: u64) -> bool;
}

View File

@ -41,12 +41,24 @@ pub const MAX_PACKET_SIZE: u8 = 64;
/// The logger handle, which contains a pipe with configurable size for buffering log messages.
pub struct UsbLogger<const N: usize> {
buffer: Pipe<CS, N>,
custom_style: Option<fn(&Record, &mut Writer<'_, N>) -> ()>,
}
impl<const N: usize> UsbLogger<N> {
/// Create a new logger instance.
pub const fn new() -> Self {
Self { buffer: Pipe::new() }
Self {
buffer: Pipe::new(),
custom_style: None,
}
}
/// Create a new logger instance with a custom formatter.
pub const fn with_custom_style(custom_style: fn(&Record, &mut Writer<'_, N>) -> ()) -> Self {
Self {
buffer: Pipe::new(),
custom_style: Some(custom_style),
}
}
/// Run the USB logger using the state and USB driver. Never returns.
@ -137,14 +149,19 @@ impl<const N: usize> log::Log for UsbLogger<N> {
fn log(&self, record: &Record) {
if self.enabled(record.metadata()) {
let _ = write!(Writer(&self.buffer), "{}\r\n", record.args());
if let Some(custom_style) = self.custom_style {
custom_style(record, &mut Writer(&self.buffer));
} else {
let _ = write!(Writer(&self.buffer), "{}\r\n", record.args());
}
}
}
fn flush(&self) {}
}
struct Writer<'d, const N: usize>(&'d Pipe<CS, N>);
/// A writer that writes to the USB logger buffer.
pub struct Writer<'d, const N: usize>(&'d Pipe<CS, N>);
impl<'d, const N: usize> core::fmt::Write for Writer<'d, N> {
fn write_str(&mut self, s: &str) -> Result<(), core::fmt::Error> {
@ -210,3 +227,33 @@ macro_rules! with_class {
LOGGER.create_future_from_class($p)
}};
}
/// Initialize the USB serial logger from a serial class and return the future to run it.
/// This version of the macro allows for a custom style function to be passed in.
/// The custom style function will be called for each log record and is responsible for writing the log message to the buffer.
///
/// Arguments specify the buffer size, log level, the serial class and the custom style function, respectively.
///
/// # Usage
///
/// ```
/// let log_fut = embassy_usb_logger::with_custom_style!(1024, log::LevelFilter::Info, logger_class, |record, writer| {
/// use core::fmt::Write;
/// let level = record.level().as_str();
/// write!(writer, "[{level}] {}\r\n", record.args()).unwrap();
/// });
/// ```
///
/// # Safety
///
/// This macro should only be invoked only once since it is setting the global logging state of the application.
#[macro_export]
macro_rules! with_custom_style {
( $x:expr, $l:expr, $p:ident, $s:expr ) => {{
static LOGGER: ::embassy_usb_logger::UsbLogger<$x> = ::embassy_usb_logger::UsbLogger::with_custom_style($s);
unsafe {
let _ = ::log::set_logger_racy(&LOGGER).map(|()| log::set_max_level_racy($l));
}
LOGGER.create_future_from_class($p)
}};
}

View File

@ -179,6 +179,8 @@ pub enum PhyType {
///
/// Available on a few STM32 chips.
InternalHighSpeed,
/// External ULPI Full-Speed PHY (or High-Speed PHY in Full-Speed mode)
ExternalFullSpeed,
/// External ULPI High-Speed PHY
ExternalHighSpeed,
}
@ -188,14 +190,14 @@ impl PhyType {
pub fn internal(&self) -> bool {
match self {
PhyType::InternalFullSpeed | PhyType::InternalHighSpeed => true,
PhyType::ExternalHighSpeed => false,
PhyType::ExternalHighSpeed | PhyType::ExternalFullSpeed => false,
}
}
/// Get whether this PHY is any of the high-speed types.
pub fn high_speed(&self) -> bool {
match self {
PhyType::InternalFullSpeed => false,
PhyType::InternalFullSpeed | PhyType::ExternalFullSpeed => false,
PhyType::ExternalHighSpeed | PhyType::InternalHighSpeed => true,
}
}
@ -204,6 +206,7 @@ impl PhyType {
match self {
PhyType::InternalFullSpeed => vals::Dspd::FULL_SPEED_INTERNAL,
PhyType::InternalHighSpeed => vals::Dspd::HIGH_SPEED,
PhyType::ExternalFullSpeed => vals::Dspd::FULL_SPEED_EXTERNAL,
PhyType::ExternalHighSpeed => vals::Dspd::HIGH_SPEED,
}
}
@ -581,6 +584,29 @@ impl<'d, const MAX_EP_COUNT: usize> Bus<'d, MAX_EP_COUNT> {
});
}
/// Applies configuration specific to
/// Core ID 0x0000_5000
pub fn config_v5(&mut self) {
let r = self.instance.regs;
let phy_type = self.instance.phy_type;
if phy_type == PhyType::InternalHighSpeed {
r.gccfg_v3().modify(|w| {
w.set_vbvaloven(!self.config.vbus_detection);
w.set_vbvaloval(!self.config.vbus_detection);
w.set_vbden(self.config.vbus_detection);
});
} else {
r.gotgctl().modify(|w| {
w.set_bvaloen(!self.config.vbus_detection);
w.set_bvaloval(!self.config.vbus_detection);
});
r.gccfg_v3().modify(|w| {
w.set_vbden(self.config.vbus_detection);
});
}
}
fn init(&mut self) {
let r = self.instance.regs;
let phy_type = self.instance.phy_type;
@ -1068,6 +1094,21 @@ impl<'d> embassy_usb_driver::EndpointOut for Endpoint<'d, Out> {
w.set_pktcnt(1);
});
if self.info.ep_type == EndpointType::Isochronous {
// Isochronous endpoints must set the correct even/odd frame bit to
// correspond with the next frame's number.
let frame_number = self.regs.dsts().read().fnsof();
let frame_is_odd = frame_number & 0x01 == 1;
self.regs.doepctl(index).modify(|r| {
if frame_is_odd {
r.set_sd0pid_sevnfrm(true);
} else {
r.set_sd1pid_soddfrm(true);
}
});
}
// Clear NAK to indicate we are ready to receive more data
self.regs.doepctl(index).modify(|w| {
w.set_cnak(true);
@ -1155,6 +1196,21 @@ impl<'d> embassy_usb_driver::EndpointIn for Endpoint<'d, In> {
w.set_xfrsiz(buf.len() as _);
});
if self.info.ep_type == EndpointType::Isochronous {
// Isochronous endpoints must set the correct even/odd frame bit to
// correspond with the next frame's number.
let frame_number = self.regs.dsts().read().fnsof();
let frame_is_odd = frame_number & 0x01 == 1;
self.regs.diepctl(index).modify(|r| {
if frame_is_odd {
r.set_sd0pid_sevnfrm(true);
} else {
r.set_sd1pid_soddfrm(true);
}
});
}
// Enable endpoint
self.regs.diepctl(index).modify(|w| {
w.set_cnak(true);

View File

@ -186,6 +186,11 @@ impl Otg {
pub const fn gccfg_v2(self) -> Reg<regs::GccfgV2, RW> {
unsafe { Reg::from_ptr(self.ptr.add(0x38usize) as _) }
}
#[doc = "General core configuration register, for core_id 0x0000_5xxx"]
#[inline(always)]
pub const fn gccfg_v3(self) -> Reg<regs::GccfgV3, RW> {
unsafe { Reg::from_ptr(self.ptr.add(0x38usize) as _) }
}
#[doc = "Core ID register"]
#[inline(always)]
pub const fn cid(self) -> Reg<regs::Cid, RW> {
@ -795,15 +800,15 @@ pub mod regs {
pub fn set_sd0pid_sevnfrm(&mut self, val: bool) {
self.0 = (self.0 & !(0x01 << 28usize)) | (((val as u32) & 0x01) << 28usize);
}
#[doc = "SODDFRM/SD1PID"]
#[doc = "SD1PID/SODDFRM"]
#[inline(always)]
pub const fn soddfrm_sd1pid(&self) -> bool {
pub const fn sd1pid_soddfrm(&self) -> bool {
let val = (self.0 >> 29usize) & 0x01;
val != 0
}
#[doc = "SODDFRM/SD1PID"]
#[doc = "SD1PID/SODDFRM"]
#[inline(always)]
pub fn set_soddfrm_sd1pid(&mut self, val: bool) {
pub fn set_sd1pid_soddfrm(&mut self, val: bool) {
self.0 = (self.0 & !(0x01 << 29usize)) | (((val as u32) & 0x01) << 29usize);
}
#[doc = "EPDIS"]
@ -1174,15 +1179,15 @@ pub mod regs {
pub fn set_sd0pid_sevnfrm(&mut self, val: bool) {
self.0 = (self.0 & !(0x01 << 28usize)) | (((val as u32) & 0x01) << 28usize);
}
#[doc = "SODDFRM"]
#[doc = "SD1PID/SODDFRM"]
#[inline(always)]
pub const fn soddfrm(&self) -> bool {
pub const fn sd1pid_soddfrm(&self) -> bool {
let val = (self.0 >> 29usize) & 0x01;
val != 0
}
#[doc = "SODDFRM"]
#[doc = "SD1PID/SODDFRM"]
#[inline(always)]
pub fn set_soddfrm(&mut self, val: bool) {
pub fn set_sd1pid_soddfrm(&mut self, val: bool) {
self.0 = (self.0 & !(0x01 << 29usize)) | (((val as u32) & 0x01) << 29usize);
}
#[doc = "EPDIS"]
@ -1831,6 +1836,172 @@ pub mod regs {
GccfgV2(0)
}
}
#[doc = "OTG general core configuration register."]
#[repr(transparent)]
#[derive(Copy, Clone, Eq, PartialEq)]
pub struct GccfgV3(pub u32);
impl GccfgV3 {
#[doc = "Charger detection, result of the current mode (primary or secondary)."]
#[inline(always)]
pub const fn chgdet(&self) -> bool {
let val = (self.0 >> 0usize) & 0x01;
val != 0
}
#[doc = "Charger detection, result of the current mode (primary or secondary)."]
#[inline(always)]
pub fn set_chgdet(&mut self, val: bool) {
self.0 = (self.0 & !(0x01 << 0usize)) | (((val as u32) & 0x01) << 0usize);
}
#[doc = "Single-Ended DP indicator This bit gives the voltage level on DP (also result of the comparison with V<sub>LGC</sub> threshold as defined in BC v1.2 standard)."]
#[inline(always)]
pub const fn fsvplus(&self) -> bool {
let val = (self.0 >> 1usize) & 0x01;
val != 0
}
#[doc = "Single-Ended DP indicator This bit gives the voltage level on DP (also result of the comparison with V<sub>LGC</sub> threshold as defined in BC v1.2 standard)."]
#[inline(always)]
pub fn set_fsvplus(&mut self, val: bool) {
self.0 = (self.0 & !(0x01 << 1usize)) | (((val as u32) & 0x01) << 1usize);
}
#[doc = "Single-Ended DM indicator This bit gives the voltage level on DM (also result of the comparison with V<sub>LGC</sub> threshold as defined in BC v1.2 standard)."]
#[inline(always)]
pub const fn fsvminus(&self) -> bool {
let val = (self.0 >> 2usize) & 0x01;
val != 0
}
#[doc = "Single-Ended DM indicator This bit gives the voltage level on DM (also result of the comparison with V<sub>LGC</sub> threshold as defined in BC v1.2 standard)."]
#[inline(always)]
pub fn set_fsvminus(&mut self, val: bool) {
self.0 = (self.0 & !(0x01 << 2usize)) | (((val as u32) & 0x01) << 2usize);
}
#[doc = "VBUS session indicator Indicates if VBUS is above VBUS session threshold."]
#[inline(always)]
pub const fn sessvld(&self) -> bool {
let val = (self.0 >> 3usize) & 0x01;
val != 0
}
#[doc = "VBUS session indicator Indicates if VBUS is above VBUS session threshold."]
#[inline(always)]
pub fn set_sessvld(&mut self, val: bool) {
self.0 = (self.0 & !(0x01 << 3usize)) | (((val as u32) & 0x01) << 3usize);
}
#[doc = "Host CDP behavior enable."]
#[inline(always)]
pub const fn hcdpen(&self) -> bool {
let val = (self.0 >> 16usize) & 0x01;
val != 0
}
#[doc = "Host CDP behavior enable."]
#[inline(always)]
pub fn set_hcdpen(&mut self, val: bool) {
self.0 = (self.0 & !(0x01 << 16usize)) | (((val as u32) & 0x01) << 16usize);
}
#[doc = "Host CDP port voltage detector enable on DP."]
#[inline(always)]
pub const fn hcdpdeten(&self) -> bool {
let val = (self.0 >> 17usize) & 0x01;
val != 0
}
#[doc = "Host CDP port voltage detector enable on DP."]
#[inline(always)]
pub fn set_hcdpdeten(&mut self, val: bool) {
self.0 = (self.0 & !(0x01 << 17usize)) | (((val as u32) & 0x01) << 17usize);
}
#[doc = "Host CDP port Voltage source enable on DM."]
#[inline(always)]
pub const fn hvdmsrcen(&self) -> bool {
let val = (self.0 >> 18usize) & 0x01;
val != 0
}
#[doc = "Host CDP port Voltage source enable on DM."]
#[inline(always)]
pub fn set_hvdmsrcen(&mut self, val: bool) {
self.0 = (self.0 & !(0x01 << 18usize)) | (((val as u32) & 0x01) << 18usize);
}
#[doc = "Data Contact Detection enable."]
#[inline(always)]
pub const fn dcden(&self) -> bool {
let val = (self.0 >> 19usize) & 0x01;
val != 0
}
#[doc = "Data Contact Detection enable."]
#[inline(always)]
pub fn set_dcden(&mut self, val: bool) {
self.0 = (self.0 & !(0x01 << 19usize)) | (((val as u32) & 0x01) << 19usize);
}
#[doc = "Primary detection enable."]
#[inline(always)]
pub const fn pden(&self) -> bool {
let val = (self.0 >> 20usize) & 0x01;
val != 0
}
#[doc = "Primary detection enable."]
#[inline(always)]
pub fn set_pden(&mut self, val: bool) {
self.0 = (self.0 & !(0x01 << 20usize)) | (((val as u32) & 0x01) << 20usize);
}
#[doc = "VBUS detection enable Enables VBUS Sensing Comparators in order to detect VBUS presence and/or perform OTG operation."]
#[inline(always)]
pub const fn vbden(&self) -> bool {
let val = (self.0 >> 21usize) & 0x01;
val != 0
}
#[doc = "VBUS detection enable Enables VBUS Sensing Comparators in order to detect VBUS presence and/or perform OTG operation."]
#[inline(always)]
pub fn set_vbden(&mut self, val: bool) {
self.0 = (self.0 & !(0x01 << 21usize)) | (((val as u32) & 0x01) << 21usize);
}
#[doc = "Secondary detection enable."]
#[inline(always)]
pub const fn sden(&self) -> bool {
let val = (self.0 >> 22usize) & 0x01;
val != 0
}
#[doc = "Secondary detection enable."]
#[inline(always)]
pub fn set_sden(&mut self, val: bool) {
self.0 = (self.0 & !(0x01 << 22usize)) | (((val as u32) & 0x01) << 22usize);
}
#[doc = "Software override value of the VBUS B-session detection."]
#[inline(always)]
pub const fn vbvaloval(&self) -> bool {
let val = (self.0 >> 23usize) & 0x01;
val != 0
}
#[doc = "Software override value of the VBUS B-session detection."]
#[inline(always)]
pub fn set_vbvaloval(&mut self, val: bool) {
self.0 = (self.0 & !(0x01 << 23usize)) | (((val as u32) & 0x01) << 23usize);
}
#[doc = "Enables a software override of the VBUS B-session detection."]
#[inline(always)]
pub const fn vbvaloven(&self) -> bool {
let val = (self.0 >> 24usize) & 0x01;
val != 0
}
#[doc = "Enables a software override of the VBUS B-session detection."]
#[inline(always)]
pub fn set_vbvaloven(&mut self, val: bool) {
self.0 = (self.0 & !(0x01 << 24usize)) | (((val as u32) & 0x01) << 24usize);
}
#[doc = "Force host mode pull-downs If the ID pin functions are enabled, the host mode pull-downs on DP and DM activate automatically. However, whenever that is not the case, yet host mode is required, this bit must be used to force the pull-downs active."]
#[inline(always)]
pub const fn forcehostpd(&self) -> bool {
let val = (self.0 >> 25usize) & 0x01;
val != 0
}
#[doc = "Force host mode pull-downs If the ID pin functions are enabled, the host mode pull-downs on DP and DM activate automatically. However, whenever that is not the case, yet host mode is required, this bit must be used to force the pull-downs active."]
#[inline(always)]
pub fn set_forcehostpd(&mut self, val: bool) {
self.0 = (self.0 & !(0x01 << 25usize)) | (((val as u32) & 0x01) << 25usize);
}
}
impl Default for GccfgV3 {
#[inline(always)]
fn default() -> GccfgV3 {
GccfgV3(0)
}
}
#[doc = "I2C access register"]
#[repr(transparent)]
#[derive(Copy, Clone, Eq, PartialEq)]

View File

@ -1,8 +1,8 @@
use heapless::Vec;
use crate::config::MAX_HANDLER_COUNT;
use crate::descriptor::{BosWriter, DescriptorWriter};
use crate::driver::{Driver, Endpoint, EndpointType};
use crate::descriptor::{BosWriter, DescriptorWriter, SynchronizationType, UsageType};
use crate::driver::{Driver, Endpoint, EndpointInfo, EndpointType};
use crate::msos::{DeviceLevelDescriptor, FunctionLevelDescriptor, MsOsDescriptorWriter};
use crate::types::{InterfaceNumber, StringIndex};
use crate::{Handler, Interface, UsbDevice, MAX_INTERFACE_COUNT, STRING_INDEX_CUSTOM_START};
@ -414,7 +414,7 @@ impl<'a, 'd, D: Driver<'d>> InterfaceAltBuilder<'a, 'd, D> {
/// Descriptors are written in the order builder functions are called. Note that some
/// classes care about the order.
pub fn descriptor(&mut self, descriptor_type: u8, descriptor: &[u8]) {
self.builder.config_descriptor.write(descriptor_type, descriptor);
self.builder.config_descriptor.write(descriptor_type, descriptor, &[]);
}
/// Add a custom Binary Object Store (BOS) descriptor to this alternate setting.
@ -422,26 +422,80 @@ impl<'a, 'd, D: Driver<'d>> InterfaceAltBuilder<'a, 'd, D> {
self.builder.bos_descriptor.capability(capability_type, capability);
}
fn endpoint_in(&mut self, ep_type: EndpointType, max_packet_size: u16, interval_ms: u8) -> D::EndpointIn {
/// Write a custom endpoint descriptor for a certain endpoint.
///
/// This can be necessary, if the endpoint descriptors can only be written
/// after the endpoint was created. As an example, an endpoint descriptor
/// may contain the address of an endpoint that was allocated earlier.
pub fn endpoint_descriptor(
&mut self,
endpoint: &EndpointInfo,
synchronization_type: SynchronizationType,
usage_type: UsageType,
extra_fields: &[u8],
) {
self.builder
.config_descriptor
.endpoint(endpoint, synchronization_type, usage_type, extra_fields);
}
/// Allocate an IN endpoint, without writing its descriptor.
///
/// Used for granular control over the order of endpoint and descriptor creation.
pub fn alloc_endpoint_in(&mut self, ep_type: EndpointType, max_packet_size: u16, interval_ms: u8) -> D::EndpointIn {
let ep = self
.builder
.driver
.alloc_endpoint_in(ep_type, max_packet_size, interval_ms)
.expect("alloc_endpoint_in failed");
self.builder.config_descriptor.endpoint(ep.info());
ep
}
fn endpoint_in(
&mut self,
ep_type: EndpointType,
max_packet_size: u16,
interval_ms: u8,
synchronization_type: SynchronizationType,
usage_type: UsageType,
extra_fields: &[u8],
) -> D::EndpointIn {
let ep = self.alloc_endpoint_in(ep_type, max_packet_size, interval_ms);
self.endpoint_descriptor(ep.info(), synchronization_type, usage_type, extra_fields);
ep
}
fn endpoint_out(&mut self, ep_type: EndpointType, max_packet_size: u16, interval_ms: u8) -> D::EndpointOut {
/// Allocate an OUT endpoint, without writing its descriptor.
///
/// Use for granular control over the order of endpoint and descriptor creation.
pub fn alloc_endpoint_out(
&mut self,
ep_type: EndpointType,
max_packet_size: u16,
interval_ms: u8,
) -> D::EndpointOut {
let ep = self
.builder
.driver
.alloc_endpoint_out(ep_type, max_packet_size, interval_ms)
.expect("alloc_endpoint_out failed");
self.builder.config_descriptor.endpoint(ep.info());
ep
}
fn endpoint_out(
&mut self,
ep_type: EndpointType,
max_packet_size: u16,
interval_ms: u8,
synchronization_type: SynchronizationType,
usage_type: UsageType,
extra_fields: &[u8],
) -> D::EndpointOut {
let ep = self.alloc_endpoint_out(ep_type, max_packet_size, interval_ms);
self.endpoint_descriptor(ep.info(), synchronization_type, usage_type, extra_fields);
ep
}
@ -451,7 +505,14 @@ impl<'a, 'd, D: Driver<'d>> InterfaceAltBuilder<'a, 'd, D> {
/// Descriptors are written in the order builder functions are called. Note that some
/// classes care about the order.
pub fn endpoint_bulk_in(&mut self, max_packet_size: u16) -> D::EndpointIn {
self.endpoint_in(EndpointType::Bulk, max_packet_size, 0)
self.endpoint_in(
EndpointType::Bulk,
max_packet_size,
0,
SynchronizationType::NoSynchronization,
UsageType::DataEndpoint,
&[],
)
}
/// Allocate a BULK OUT endpoint and write its descriptor.
@ -459,7 +520,14 @@ impl<'a, 'd, D: Driver<'d>> InterfaceAltBuilder<'a, 'd, D> {
/// Descriptors are written in the order builder functions are called. Note that some
/// classes care about the order.
pub fn endpoint_bulk_out(&mut self, max_packet_size: u16) -> D::EndpointOut {
self.endpoint_out(EndpointType::Bulk, max_packet_size, 0)
self.endpoint_out(
EndpointType::Bulk,
max_packet_size,
0,
SynchronizationType::NoSynchronization,
UsageType::DataEndpoint,
&[],
)
}
/// Allocate a INTERRUPT IN endpoint and write its descriptor.
@ -467,24 +535,66 @@ impl<'a, 'd, D: Driver<'d>> InterfaceAltBuilder<'a, 'd, D> {
/// Descriptors are written in the order builder functions are called. Note that some
/// classes care about the order.
pub fn endpoint_interrupt_in(&mut self, max_packet_size: u16, interval_ms: u8) -> D::EndpointIn {
self.endpoint_in(EndpointType::Interrupt, max_packet_size, interval_ms)
self.endpoint_in(
EndpointType::Interrupt,
max_packet_size,
interval_ms,
SynchronizationType::NoSynchronization,
UsageType::DataEndpoint,
&[],
)
}
/// Allocate a INTERRUPT OUT endpoint and write its descriptor.
pub fn endpoint_interrupt_out(&mut self, max_packet_size: u16, interval_ms: u8) -> D::EndpointOut {
self.endpoint_out(EndpointType::Interrupt, max_packet_size, interval_ms)
self.endpoint_out(
EndpointType::Interrupt,
max_packet_size,
interval_ms,
SynchronizationType::NoSynchronization,
UsageType::DataEndpoint,
&[],
)
}
/// Allocate a ISOCHRONOUS IN endpoint and write its descriptor.
///
/// Descriptors are written in the order builder functions are called. Note that some
/// classes care about the order.
pub fn endpoint_isochronous_in(&mut self, max_packet_size: u16, interval_ms: u8) -> D::EndpointIn {
self.endpoint_in(EndpointType::Isochronous, max_packet_size, interval_ms)
pub fn endpoint_isochronous_in(
&mut self,
max_packet_size: u16,
interval_ms: u8,
synchronization_type: SynchronizationType,
usage_type: UsageType,
extra_fields: &[u8],
) -> D::EndpointIn {
self.endpoint_in(
EndpointType::Isochronous,
max_packet_size,
interval_ms,
synchronization_type,
usage_type,
extra_fields,
)
}
/// Allocate a ISOCHRONOUS OUT endpoint and write its descriptor.
pub fn endpoint_isochronous_out(&mut self, max_packet_size: u16, interval_ms: u8) -> D::EndpointOut {
self.endpoint_out(EndpointType::Isochronous, max_packet_size, interval_ms)
pub fn endpoint_isochronous_out(
&mut self,
max_packet_size: u16,
interval_ms: u8,
synchronization_type: SynchronizationType,
usage_type: UsageType,
extra_fields: &[u8],
) -> D::EndpointOut {
self.endpoint_out(
EndpointType::Isochronous,
max_packet_size,
interval_ms,
synchronization_type,
usage_type,
extra_fields,
)
}
}

View File

@ -1,4 +1,5 @@
//! Utilities for writing USB descriptors.
use embassy_usb_driver::EndpointType;
use crate::builder::Config;
use crate::driver::EndpointInfo;
@ -38,6 +39,40 @@ pub mod capability_type {
pub const PLATFORM: u8 = 5;
}
/// USB endpoint synchronization type. The values of this enum can be directly
/// cast into `u8` to get the bmAttributes synchronization type bits.
/// Values other than `NoSynchronization` are only allowed on isochronous endpoints.
#[repr(u8)]
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum SynchronizationType {
/// No synchronization is used.
NoSynchronization = 0b00,
/// Unsynchronized, although sinks provide data rate feedback.
Asynchronous = 0b01,
/// Synchronized using feedback or feedforward data rate information.
Adaptive = 0b10,
/// Synchronized to the USBs SOF.
Synchronous = 0b11,
}
/// USB endpoint usage type. The values of this enum can be directly cast into
/// `u8` to get the bmAttributes usage type bits.
/// Values other than `DataEndpoint` are only allowed on isochronous endpoints.
#[repr(u8)]
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum UsageType {
/// Use the endpoint for regular data transfer.
DataEndpoint = 0b00,
/// Endpoint conveys explicit feedback information for one or more data endpoints.
FeedbackEndpoint = 0b01,
/// A data endpoint that also serves as an implicit feedback endpoint for one or more data endpoints.
ImplicitFeedbackDataEndpoint = 0b10,
/// Reserved usage type.
Reserved = 0b11,
}
/// A writer for USB descriptors.
pub(crate) struct DescriptorWriter<'a> {
pub buf: &'a mut [u8],
@ -65,23 +100,26 @@ impl<'a> DescriptorWriter<'a> {
self.position
}
/// Writes an arbitrary (usually class-specific) descriptor.
pub fn write(&mut self, descriptor_type: u8, descriptor: &[u8]) {
let length = descriptor.len();
/// Writes an arbitrary (usually class-specific) descriptor with optional extra fields.
pub fn write(&mut self, descriptor_type: u8, descriptor: &[u8], extra_fields: &[u8]) {
let descriptor_length = descriptor.len();
let extra_fields_length = extra_fields.len();
let total_length = descriptor_length + extra_fields_length;
assert!(
(self.position + 2 + length) <= self.buf.len() && (length + 2) <= 255,
(self.position + 2 + total_length) <= self.buf.len() && (total_length + 2) <= 255,
"Descriptor buffer full"
);
self.buf[self.position] = (length + 2) as u8;
self.buf[self.position] = (total_length + 2) as u8;
self.buf[self.position + 1] = descriptor_type;
let start = self.position + 2;
self.buf[start..start + length].copy_from_slice(descriptor);
self.buf[start..start + descriptor_length].copy_from_slice(descriptor);
self.buf[start + descriptor_length..start + total_length].copy_from_slice(extra_fields);
self.position = start + length;
self.position = start + total_length;
}
pub(crate) fn configuration(&mut self, config: &Config) {
@ -99,6 +137,7 @@ impl<'a> DescriptorWriter<'a> {
| if config.supports_remote_wakeup { 0x20 } else { 0x00 }, // bmAttributes
(config.max_power / 2) as u8, // bMaxPower
],
&[],
);
}
@ -145,6 +184,7 @@ impl<'a> DescriptorWriter<'a> {
function_protocol,
0,
],
&[],
);
}
@ -195,6 +235,7 @@ impl<'a> DescriptorWriter<'a> {
interface_protocol, // bInterfaceProtocol
str_index, // iInterface
],
&[],
);
}
@ -204,21 +245,50 @@ impl<'a> DescriptorWriter<'a> {
///
/// * `endpoint` - Endpoint previously allocated with
/// [`UsbDeviceBuilder`](crate::bus::UsbDeviceBuilder).
pub fn endpoint(&mut self, endpoint: &EndpointInfo) {
/// * `synchronization_type` - The synchronization type of the endpoint.
/// * `usage_type` - The usage type of the endpoint.
/// * `extra_fields` - Additional, class-specific entries at the end of the endpoint descriptor.
pub fn endpoint(
&mut self,
endpoint: &EndpointInfo,
synchronization_type: SynchronizationType,
usage_type: UsageType,
extra_fields: &[u8],
) {
match self.num_endpoints_mark {
Some(mark) => self.buf[mark] += 1,
None => panic!("you can only call `endpoint` after `interface/interface_alt`."),
};
let mut bm_attributes = endpoint.ep_type as u8;
// Synchronization types other than `NoSynchronization`,
// and usage types other than `DataEndpoint`
// are only allowed for isochronous endpoints.
if endpoint.ep_type != EndpointType::Isochronous {
assert_eq!(synchronization_type, SynchronizationType::NoSynchronization);
assert_eq!(usage_type, UsageType::DataEndpoint);
} else {
if usage_type == UsageType::FeedbackEndpoint {
assert_eq!(synchronization_type, SynchronizationType::NoSynchronization)
}
let synchronization_bm_attibutes: u8 = (synchronization_type as u8) << 2;
let usage_bm_attibutes: u8 = (usage_type as u8) << 4;
bm_attributes |= usage_bm_attibutes | synchronization_bm_attibutes;
}
self.write(
descriptor_type::ENDPOINT,
&[
endpoint.addr.into(), // bEndpointAddress
endpoint.ep_type as u8, // bmAttributes
endpoint.addr.into(), // bEndpointAddress
bm_attributes, // bmAttributes
endpoint.max_packet_size as u8,
(endpoint.max_packet_size >> 8) as u8, // wMaxPacketSize
endpoint.interval_ms, // bInterval
],
extra_fields,
);
}
@ -308,6 +378,9 @@ impl<'a> BosWriter<'a> {
}
pub(crate) fn bos(&mut self) {
if (self.writer.buf.len() - self.writer.position) < 5 {
return;
}
self.num_caps_mark = Some(self.writer.position + 4);
self.writer.write(
descriptor_type::BOS,
@ -315,6 +388,7 @@ impl<'a> BosWriter<'a> {
0x00, 0x00, // wTotalLength
0x00, // bNumDeviceCaps
],
&[],
);
self.capability(capability_type::USB_2_0_EXTENSION, &[0; 4]);
@ -350,6 +424,9 @@ impl<'a> BosWriter<'a> {
}
pub(crate) fn end_bos(&mut self) {
if self.writer.position == 0 {
return;
}
self.num_caps_mark = None;
let position = self.writer.position as u16;
self.writer.buf[2..4].copy_from_slice(&position.to_le_bytes());

View File

@ -31,4 +31,7 @@ fn main() {
println!("cargo:rustc-link-arg-bins=--nmagic");
println!("cargo:rustc-link-arg-bins=-Tlink.x");
if env::var("CARGO_FEATURE_DEFMT").is_ok() {
println!("cargo:rustc-link-arg-bins=-Tdefmt.x");
}
}

View File

@ -2,6 +2,9 @@
#![no_main]
#![macro_use]
#[cfg(feature = "defmt")]
use defmt_rtt as _;
use embassy_boot::State;
use embassy_boot_nrf::{FirmwareUpdater, FirmwareUpdaterConfig};
use embassy_embedded_hal::adapter::BlockingAsync;
use embassy_executor::Spawner;
@ -22,6 +25,7 @@ async fn main(_spawner: Spawner) {
let mut button = Input::new(p.P0_11, Pull::Up);
let mut led = Output::new(p.P0_13, Level::Low, OutputDrive::Standard);
let mut led_reverted = Output::new(p.P0_14, Level::High, OutputDrive::Standard);
//let mut led = Output::new(p.P1_10, Level::Low, OutputDrive::Standard);
//let mut button = Input::new(p.P1_02, Pull::Up);
@ -53,6 +57,13 @@ async fn main(_spawner: Spawner) {
let config = FirmwareUpdaterConfig::from_linkerfile(&nvmc, &nvmc);
let mut magic = [0; 4];
let mut updater = FirmwareUpdater::new(config, &mut magic);
let state = updater.get_state().await.unwrap();
if state == State::Revert {
led_reverted.set_low();
} else {
led_reverted.set_high();
}
loop {
led.set_low();
button.wait_for_any_edge().await;

View File

@ -35,8 +35,8 @@ async fn main(spawner: Spawner) {
loop {
match CHANNEL.receive().await {
LedState::On => led.set_high(),
LedState::Off => led.set_low(),
LedState::On => led.set_low(),
LedState::Off => led.set_high(),
}
}
}

View File

@ -33,8 +33,8 @@ async fn recv_task(led: AnyPin, receiver: Receiver<'static, NoopRawMutex, LedSta
loop {
match receiver.receive().await {
LedState::On => led.set_high(),
LedState::Off => led.set_low(),
LedState::On => led.set_low(),
LedState::Off => led.set_high(),
}
}
}

View File

@ -4,7 +4,7 @@
use defmt::*;
use embassy_executor::Spawner;
use embassy_net::tcp::TcpSocket;
use embassy_net::{Stack, StackResources};
use embassy_net::StackResources;
use embassy_net_enc28j60::Enc28j60;
use embassy_nrf::gpio::{Level, Output, OutputDrive};
use embassy_nrf::rng::Rng;
@ -23,11 +23,12 @@ bind_interrupts!(struct Irqs {
#[embassy_executor::task]
async fn net_task(
stack: &'static Stack<
mut runner: embassy_net::Runner<
'static,
Enc28j60<ExclusiveDevice<Spim<'static, peripherals::SPI3>, Output<'static>, Delay>, Output<'static>>,
>,
) -> ! {
stack.run().await
runner.run().await
}
#[embassy_executor::main]
@ -67,12 +68,9 @@ async fn main(spawner: Spawner) {
// Init network stack
static RESOURCES: StaticCell<StackResources<3>> = StaticCell::new();
static STACK: StaticCell<
Stack<Enc28j60<ExclusiveDevice<Spim<'static, peripherals::SPI3>, Output<'static>, Delay>, Output<'static>>>,
> = StaticCell::new();
let stack = STACK.init(Stack::new(device, config, RESOURCES.init(StackResources::new()), seed));
let (stack, runner) = embassy_net::new(device, config, RESOURCES.init(StackResources::new()), seed);
unwrap!(spawner.spawn(net_task(stack)));
unwrap!(spawner.spawn(net_task(runner)));
// And now we can use it!

View File

@ -6,7 +6,7 @@ use core::mem;
use defmt::*;
use embassy_executor::Spawner;
use embassy_net::tcp::TcpSocket;
use embassy_net::{Stack, StackResources};
use embassy_net::StackResources;
use embassy_nrf::rng::Rng;
use embassy_nrf::usb::vbus_detect::HardwareVbusDetect;
use embassy_nrf::usb::Driver;
@ -39,8 +39,8 @@ async fn usb_ncm_task(class: Runner<'static, MyDriver, MTU>) -> ! {
}
#[embassy_executor::task]
async fn net_task(stack: &'static Stack<Device<'static, MTU>>) -> ! {
stack.run().await
async fn net_task(mut runner: embassy_net::Runner<'static, Device<'static, MTU>>) -> ! {
runner.run().await
}
#[embassy_executor::main]
@ -116,10 +116,9 @@ async fn main(spawner: Spawner) {
// Init network stack
static RESOURCES: StaticCell<StackResources<3>> = StaticCell::new();
static STACK: StaticCell<Stack<Device<'static, MTU>>> = StaticCell::new();
let stack = &*STACK.init(Stack::new(device, config, RESOURCES.init(StackResources::new()), seed));
let (stack, runner) = embassy_net::new(device, config, RESOURCES.init(StackResources::new()), seed);
unwrap!(spawner.spawn(net_task(stack)));
unwrap!(spawner.spawn(net_task(runner)));
// And now we can use it!

View File

@ -4,7 +4,7 @@
use defmt::{info, unwrap, warn};
use embassy_executor::Spawner;
use embassy_net::tcp::TcpSocket;
use embassy_net::{Stack, StackResources};
use embassy_net::StackResources;
use embassy_nrf::gpio::{Input, Level, Output, OutputDrive, Pull};
use embassy_nrf::rng::Rng;
use embassy_nrf::spim::{self, Spim};
@ -36,8 +36,8 @@ async fn wifi_task(
}
#[embassy_executor::task]
async fn net_task(stack: &'static Stack<hosted::NetDriver<'static>>) -> ! {
stack.run().await
async fn net_task(mut runner: embassy_net::Runner<'static, hosted::NetDriver<'static>>) -> ! {
runner.run().await
}
#[embassy_executor::main]
@ -90,10 +90,9 @@ async fn main(spawner: Spawner) {
// Init network stack
static RESOURCES: StaticCell<StackResources<3>> = StaticCell::new();
static STACK: StaticCell<Stack<hosted::NetDriver<'static>>> = StaticCell::new();
let stack = &*STACK.init(Stack::new(device, config, RESOURCES.init(StackResources::new()), seed));
let (stack, runner) = embassy_net::new(device, config, RESOURCES.init(StackResources::new()), seed);
unwrap!(spawner.spawn(net_task(stack)));
unwrap!(spawner.spawn(net_task(runner)));
// And now we can use it!

View File

@ -1,5 +1,6 @@
[target.'cfg(all(target_arch = "arm", target_os = "none"))']
runner = "probe-rs run --chip nRF9160_xxAA"
# runner = "probe-rs run --chip nRF9160_xxAA"
runner = [ "probe-rs", "run", "--chip=nRF9160_xxAA", "--always-print-stacktrace", "--log-format={t} {[{L}]%bold} {s} {{c} {ff}:{l:1}%dimmed}" ]
[build]
target = "thumbv8m.main-none-eabihf"

View File

@ -8,13 +8,19 @@ license = "MIT OR Apache-2.0"
embassy-executor = { version = "0.6.0", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] }
embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime"] }
embassy-nrf = { version = "0.2.0", path = "../../embassy-nrf", features = ["defmt", "nrf9160-s", "time-driver-rtc1", "gpiote", "unstable-pac", "time"] }
embassy-net-nrf91 = { version = "0.1.0", path = "../../embassy-net-nrf91", features = ["defmt"] }
embassy-net = { version = "0.4.0", path = "../../embassy-net", features = ["defmt", "tcp", "proto-ipv4", "medium-ip"] }
defmt = "0.3"
defmt-rtt = "0.4"
heapless = "0.8"
cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] }
cortex-m-rt = "0.7.0"
panic-probe = { version = "0.3", features = ["print-defmt"] }
static_cell = { version = "2" }
embedded-io = "0.6.1"
embedded-io-async = { version = "0.6.1", features = ["defmt-03"] }
[profile.release]
debug = 2

View File

@ -1,5 +1,9 @@
MEMORY
{
FLASH : ORIGIN = 0x00000000, LENGTH = 1024K
RAM : ORIGIN = 0x20018000, LENGTH = 160K
FLASH : ORIGIN = 0x00000000, LENGTH = 1024K
RAM : ORIGIN = 0x20010000, LENGTH = 192K
IPC : ORIGIN = 0x20000000, LENGTH = 64K
}
PROVIDE(__start_ipc = ORIGIN(IPC));
PROVIDE(__end_ipc = ORIGIN(IPC) + LENGTH(IPC));

View File

@ -0,0 +1,198 @@
#![no_std]
#![no_main]
use core::mem::MaybeUninit;
use core::net::IpAddr;
use core::ptr::addr_of_mut;
use core::slice;
use core::str::FromStr;
use defmt::{info, unwrap, warn};
use embassy_executor::Spawner;
use embassy_net::{Ipv4Address, Ipv4Cidr, Stack, StackResources};
use embassy_net_nrf91::context::Status;
use embassy_net_nrf91::{context, Runner, State, TraceBuffer, TraceReader};
use embassy_nrf::buffered_uarte::{self, BufferedUarteTx};
use embassy_nrf::gpio::{AnyPin, Level, Output, OutputDrive, Pin};
use embassy_nrf::uarte::Baudrate;
use embassy_nrf::{bind_interrupts, interrupt, peripherals, uarte};
use embassy_time::{Duration, Timer};
use embedded_io_async::Write;
use heapless::Vec;
use static_cell::StaticCell;
use {defmt_rtt as _, panic_probe as _};
#[interrupt]
fn IPC() {
embassy_net_nrf91::on_ipc_irq();
}
bind_interrupts!(struct Irqs {
UARTE0_SPIM0_SPIS0_TWIM0_TWIS0 => buffered_uarte::InterruptHandler<peripherals::SERIAL0>;
});
#[embassy_executor::task]
async fn trace_task(mut uart: BufferedUarteTx<'static, peripherals::SERIAL0>, reader: TraceReader<'static>) -> ! {
let mut rx = [0u8; 1024];
loop {
let n = reader.read(&mut rx[..]).await;
unwrap!(uart.write_all(&rx[..n]).await);
}
}
#[embassy_executor::task]
async fn modem_task(runner: Runner<'static>) -> ! {
runner.run().await
}
#[embassy_executor::task]
async fn net_task(mut runner: embassy_net::Runner<'static, embassy_net_nrf91::NetDriver<'static>>) -> ! {
runner.run().await
}
#[embassy_executor::task]
async fn control_task(
control: &'static context::Control<'static>,
config: context::Config<'static>,
stack: Stack<'static>,
) {
unwrap!(control.configure(&config).await);
unwrap!(
control
.run(|status| {
stack.set_config_v4(status_to_config(status));
})
.await
);
}
fn status_to_config(status: &Status) -> embassy_net::ConfigV4 {
let Some(IpAddr::V4(addr)) = status.ip else {
panic!("Unexpected IP address");
};
let addr = Ipv4Address(addr.octets());
let gateway = if let Some(IpAddr::V4(addr)) = status.gateway {
Some(Ipv4Address(addr.octets()))
} else {
None
};
let mut dns_servers = Vec::new();
for dns in status.dns.iter() {
if let IpAddr::V4(ip) = dns {
unwrap!(dns_servers.push(Ipv4Address(ip.octets())));
}
}
embassy_net::ConfigV4::Static(embassy_net::StaticConfigV4 {
address: Ipv4Cidr::new(addr, 32),
gateway,
dns_servers,
})
}
#[embassy_executor::task]
async fn blink_task(pin: AnyPin) {
let mut led = Output::new(pin, Level::Low, OutputDrive::Standard);
loop {
led.set_high();
Timer::after_millis(1000).await;
led.set_low();
Timer::after_millis(1000).await;
}
}
extern "C" {
static __start_ipc: u8;
static __end_ipc: u8;
}
#[embassy_executor::main]
async fn main(spawner: Spawner) {
let p = embassy_nrf::init(Default::default());
info!("Hello World!");
unwrap!(spawner.spawn(blink_task(p.P0_02.degrade())));
let ipc_mem = unsafe {
let ipc_start = &__start_ipc as *const u8 as *mut MaybeUninit<u8>;
let ipc_end = &__end_ipc as *const u8 as *mut MaybeUninit<u8>;
let ipc_len = ipc_end.offset_from(ipc_start) as usize;
slice::from_raw_parts_mut(ipc_start, ipc_len)
};
static mut TRACE_BUF: [u8; 4096] = [0u8; 4096];
let mut config = uarte::Config::default();
config.baudrate = Baudrate::BAUD1M;
let uart = BufferedUarteTx::new(
//let trace_uart = BufferedUarteTx::new(
unsafe { peripherals::SERIAL0::steal() },
Irqs,
unsafe { peripherals::P0_01::steal() },
//unsafe { peripherals::P0_14::steal() },
config,
unsafe { &mut *addr_of_mut!(TRACE_BUF) },
);
static STATE: StaticCell<State> = StaticCell::new();
static TRACE: StaticCell<TraceBuffer> = StaticCell::new();
let (device, control, runner, tracer) =
embassy_net_nrf91::new_with_trace(STATE.init(State::new()), ipc_mem, TRACE.init(TraceBuffer::new())).await;
unwrap!(spawner.spawn(modem_task(runner)));
unwrap!(spawner.spawn(trace_task(uart, tracer)));
let config = embassy_net::Config::default();
// Generate "random" seed. nRF91 has no RNG, TODO figure out something...
let seed = 123456;
// Init network stack
static RESOURCES: StaticCell<StackResources<2>> = StaticCell::new();
let (stack, runner) = embassy_net::new(device, config, RESOURCES.init(StackResources::<2>::new()), seed);
unwrap!(spawner.spawn(net_task(runner)));
static CONTROL: StaticCell<context::Control<'static>> = StaticCell::new();
let control = CONTROL.init(context::Control::new(control, 0).await);
unwrap!(spawner.spawn(control_task(
control,
context::Config {
apn: b"iot.nat.es",
auth_prot: context::AuthProt::Pap,
auth: Some((b"orange", b"orange")),
},
stack
)));
stack.wait_config_up().await;
let mut rx_buffer = [0; 4096];
let mut tx_buffer = [0; 4096];
loop {
let mut socket = embassy_net::tcp::TcpSocket::new(stack, &mut rx_buffer, &mut tx_buffer);
socket.set_timeout(Some(Duration::from_secs(10)));
info!("Connecting...");
let host_addr = embassy_net::Ipv4Address::from_str("45.79.112.203").unwrap();
if let Err(e) = socket.connect((host_addr, 4242)).await {
warn!("connect error: {:?}", e);
Timer::after_secs(10).await;
continue;
}
info!("Connected to {:?}", socket.remote_endpoint());
let msg = b"Hello world!\n";
for _ in 0..10 {
if let Err(e) = socket.write_all(msg).await {
warn!("write error: {:?}", e);
break;
}
info!("txd: {}", core::str::from_utf8(msg).unwrap());
Timer::after_secs(1).await;
}
Timer::after_secs(4).await;
}
}

View File

@ -74,7 +74,6 @@ opt-level = "z"
[patch.crates-io]
trouble-host = { git = "https://github.com/embassy-rs/trouble.git", rev = "4b8c0f499b34e46ca23a56e2d1640ede371722cf" }
bt-hci = { git = "https://github.com/alexmoon/bt-hci.git", rev = "b9cd5954f6bd89b535cad9c418e9fdf12812d7c3" }
embassy-executor = { path = "../../embassy-executor" }
embassy-sync = { path = "../../embassy-sync" }
embassy-futures = { path = "../../embassy-futures" }

View File

@ -43,8 +43,10 @@ async fn main(spawner: Spawner) {
// at hardcoded addresses, instead of baking them into the program with `include_bytes!`:
// probe-rs download 43439A0.bin --format bin --chip RP2040 --base-address 0x10100000
// probe-rs download 43439A0_clm.bin --format bin --chip RP2040 --base-address 0x10140000
// probe-rs download 43439A0_btfw.bin --format bin --chip RP2040 --base-address 0x10141400
//let fw = unsafe { core::slice::from_raw_parts(0x10100000 as *const u8, 224190) };
//let clm = unsafe { core::slice::from_raw_parts(0x10140000 as *const u8, 4752) };
//let btfw = unsafe { core::slice::from_raw_parts(0x10141400 as *const u8, 6164) };
let pwr = Output::new(p.PIN_23, Level::Low);
let cs = Output::new(p.PIN_25, Level::High);

View File

@ -36,8 +36,8 @@ async fn ethernet_task(
}
#[embassy_executor::task]
async fn net_task(stack: &'static Stack<Device<'static>>) -> ! {
stack.run().await
async fn net_task(mut runner: embassy_net::Runner<'static, Device<'static>>) -> ! {
runner.run().await
}
#[embassy_executor::main]
@ -71,17 +71,16 @@ async fn main(spawner: Spawner) {
let seed = rng.next_u64();
// Init network stack
static STACK: StaticCell<Stack<Device<'static>>> = StaticCell::new();
static RESOURCES: StaticCell<StackResources<3>> = StaticCell::new();
let stack = &*STACK.init(Stack::new(
let (stack, runner) = embassy_net::new(
device,
embassy_net::Config::dhcpv4(Default::default()),
RESOURCES.init(StackResources::new()),
seed,
));
);
// Launch network task
unwrap!(spawner.spawn(net_task(&stack)));
unwrap!(spawner.spawn(net_task(runner)));
info!("Waiting for DHCP...");
let cfg = wait_for_config(stack).await;
@ -89,12 +88,12 @@ async fn main(spawner: Spawner) {
info!("IP address: {:?}", local_addr);
// Create two sockets listening to the same port, to handle simultaneous connections
unwrap!(spawner.spawn(listen_task(&stack, 0, 1234)));
unwrap!(spawner.spawn(listen_task(&stack, 1, 1234)));
unwrap!(spawner.spawn(listen_task(stack, 0, 1234)));
unwrap!(spawner.spawn(listen_task(stack, 1, 1234)));
}
#[embassy_executor::task(pool_size = 2)]
async fn listen_task(stack: &'static Stack<Device<'static>>, id: u8, port: u16) {
async fn listen_task(stack: Stack<'static>, id: u8, port: u16) {
let mut rx_buffer = [0; 4096];
let mut tx_buffer = [0; 4096];
let mut buf = [0; 4096];
@ -131,7 +130,7 @@ async fn listen_task(stack: &'static Stack<Device<'static>>, id: u8, port: u16)
}
}
async fn wait_for_config(stack: &'static Stack<Device<'static>>) -> embassy_net::StaticConfigV4 {
async fn wait_for_config(stack: Stack<'static>) -> embassy_net::StaticConfigV4 {
loop {
if let Some(config) = stack.config_v4() {
return config.clone();

View File

@ -38,8 +38,8 @@ async fn ethernet_task(
}
#[embassy_executor::task]
async fn net_task(stack: &'static Stack<Device<'static>>) -> ! {
stack.run().await
async fn net_task(mut runner: embassy_net::Runner<'static, Device<'static>>) -> ! {
runner.run().await
}
#[embassy_executor::main]
@ -74,17 +74,16 @@ async fn main(spawner: Spawner) {
let seed = rng.next_u64();
// Init network stack
static STACK: StaticCell<Stack<Device<'static>>> = StaticCell::new();
static RESOURCES: StaticCell<StackResources<3>> = StaticCell::new();
let stack = &*STACK.init(Stack::new(
let (stack, runner) = embassy_net::new(
device,
embassy_net::Config::dhcpv4(Default::default()),
RESOURCES.init(StackResources::new()),
seed,
));
);
// Launch network task
unwrap!(spawner.spawn(net_task(&stack)));
unwrap!(spawner.spawn(net_task(runner)));
info!("Waiting for DHCP...");
let cfg = wait_for_config(stack).await;
@ -119,7 +118,7 @@ async fn main(spawner: Spawner) {
}
}
async fn wait_for_config(stack: &'static Stack<Device<'static>>) -> embassy_net::StaticConfigV4 {
async fn wait_for_config(stack: Stack<'static>) -> embassy_net::StaticConfigV4 {
loop {
if let Some(config) = stack.config_v4() {
return config.clone();

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