mirror of
https://github.com/embassy-rs/embassy.git
synced 2024-11-21 22:32:29 +00:00
Merge branch 'embassy-rs:main' into main
This commit is contained in:
commit
0f9cc53d92
4
.github/ci/test.sh
vendored
4
.github/ci/test.sh
vendored
@ -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
1
.gitignore
vendored
@ -5,3 +5,4 @@ Cargo.lock
|
||||
third_party
|
||||
/Cargo.toml
|
||||
out/
|
||||
.zed
|
||||
|
6
ci.sh
6
ci.sh
@ -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
|
||||
|
@ -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/"
|
||||
|
@ -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;
|
||||
|
@ -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")
|
||||
}
|
||||
|
||||
|
@ -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();
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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)]
|
||||
|
@ -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
|
||||
|
@ -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]
|
@ -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 = []
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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.
|
||||
|
@ -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.
|
||||
|
@ -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);
|
||||
|
@ -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"]
|
||||
|
||||
|
@ -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)]
|
||||
|
@ -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())
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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...");
|
||||
|
38
embassy-net-nrf91/Cargo.toml
Normal file
38
embassy-net-nrf91/Cargo.toml
Normal 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"]
|
9
embassy-net-nrf91/README.md
Normal file
9
embassy-net-nrf91/README.md
Normal 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.
|
350
embassy-net-nrf91/src/context.rs
Normal file
350
embassy-net-nrf91/src/context.rs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
274
embassy-net-nrf91/src/fmt.rs
Normal file
274
embassy-net-nrf91/src/fmt.rs
Normal 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
1046
embassy-net-nrf91/src/lib.rs
Normal file
File diff suppressed because it is too large
Load Diff
@ -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",
|
||||
] }
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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")]
|
@ -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!()
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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) },
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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| {
|
||||
|
@ -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()
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
108
embassy-rp/src/otp.rs
Normal 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],
|
||||
]))
|
||||
}
|
@ -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
|
||||
|
@ -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));
|
||||
|
33
embassy-rp/src/rom_data/mod.rs
Normal file
33
embassy-rp/src/rom_data/mod.rs
Normal 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::*;
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
752
embassy-rp/src/rom_data/rp235x.rs
Normal file
752
embassy-rp/src/rom_data/rp235x.rs
Normal 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 bootrom’s 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
405
embassy-rp/src/trng.rs
Normal 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 block’s 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(®.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);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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(
|
||||
|
@ -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.
|
||||
|
@ -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)),
|
||||
|
@ -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;
|
||||
|
@ -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)]
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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 => {
|
||||
|
@ -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;
|
||||
}
|
||||
|
142
embassy-stm32/src/flash/f2.rs
Normal file
142
embassy-stm32/src/flash/f2.rs
Normal 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))
|
||||
}
|
||||
}
|
||||
}
|
@ -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"
|
||||
)]
|
||||
|
@ -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)]
|
||||
|
18
embassy-stm32/src/lptim/channel.rs
Normal file
18
embassy-stm32/src/lptim/channel.rs
Normal 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,
|
||||
}
|
||||
}
|
||||
}
|
48
embassy-stm32/src/lptim/mod.rs
Normal file
48
embassy-stm32/src/lptim/mod.rs
Normal 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 {}
|
||||
};
|
||||
}
|
168
embassy-stm32/src/lptim/pwm.rs
Normal file
168
embassy-stm32/src/lptim/pwm.rs
Normal 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
|
||||
}
|
||||
}
|
18
embassy-stm32/src/lptim/timer/channel_direction.rs
Normal file
18
embassy-stm32/src/lptim/timer/channel_direction.rs
Normal 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,
|
||||
}
|
||||
}
|
||||
}
|
133
embassy-stm32/src/lptim/timer/mod.rs
Normal file
133
embassy-stm32/src/lptim/timer/mod.rs
Normal 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()
|
||||
}
|
||||
}
|
90
embassy-stm32/src/lptim/timer/prescaler.rs
Normal file
90
embassy-stm32/src/lptim/timer/prescaler.rs
Normal 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
|
||||
}
|
||||
}
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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())
|
||||
}
|
||||
}
|
||||
|
@ -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) => {
|
||||
|
@ -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) {
|
||||
|
@ -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> {
|
||||
|
@ -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> {
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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")
|
||||
|
@ -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
|
||||
|
@ -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)]
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
}};
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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)]
|
||||
|
@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -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 USB’s 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());
|
||||
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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!
|
||||
|
||||
|
@ -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!
|
||||
|
||||
|
@ -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!
|
||||
|
||||
|
@ -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"
|
||||
|
@ -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
|
||||
|
@ -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));
|
||||
|
198
examples/nrf9160/src/bin/modem_tcp_client.rs
Normal file
198
examples/nrf9160/src/bin/modem_tcp_client.rs
Normal 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;
|
||||
}
|
||||
}
|
@ -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" }
|
||||
|
@ -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);
|
||||
|
@ -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();
|
||||
|
@ -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
Loading…
Reference in New Issue
Block a user