diff options
| author | Dario Nieuwenhuis <[email protected]> | 2024-09-09 20:03:28 +0000 |
|---|---|---|
| committer | GitHub <[email protected]> | 2024-09-09 20:03:28 +0000 |
| commit | 2286e5da13b11f0cfc37e5345e3ed3c40f774055 (patch) | |
| tree | 5afa31997e3dd024135151ce64813f5db478569b | |
| parent | 0ef06cc19b61c8196fea941514ec313a0f15d145 (diff) | |
| parent | 6af1cb7a20fe15750b353e2e6af6832786f8c065 (diff) | |
Merge pull request #3105 from embassy-rs/net-nrf91
embassy-net driver for nrf91
| -rw-r--r-- | embassy-net-nrf91/Cargo.toml | 38 | ||||
| -rw-r--r-- | embassy-net-nrf91/README.md | 9 | ||||
| -rw-r--r-- | embassy-net-nrf91/src/context.rs | 350 | ||||
| -rw-r--r-- | embassy-net-nrf91/src/fmt.rs | 274 | ||||
| -rw-r--r-- | embassy-net-nrf91/src/lib.rs | 1046 | ||||
| -rw-r--r-- | embassy-nrf/src/buffered_uarte.rs | 28 | ||||
| -rw-r--r-- | examples/nrf9160/.cargo/config.toml | 3 | ||||
| -rw-r--r-- | examples/nrf9160/Cargo.toml | 6 | ||||
| -rw-r--r-- | examples/nrf9160/memory.x | 8 | ||||
| -rw-r--r-- | examples/nrf9160/src/bin/modem_tcp_client.rs | 204 |
10 files changed, 1963 insertions, 3 deletions
diff --git a/embassy-net-nrf91/Cargo.toml b/embassy-net-nrf91/Cargo.toml new file mode 100644 index 000000000..07a0c8886 --- /dev/null +++ b/embassy-net-nrf91/Cargo.toml | |||
| @@ -0,0 +1,38 @@ | |||
| 1 | [package] | ||
| 2 | name = "embassy-net-nrf91" | ||
| 3 | version = "0.1.0" | ||
| 4 | edition = "2021" | ||
| 5 | description = "embassy-net driver for Nordic nRF91-series cellular modems" | ||
| 6 | keywords = ["embedded", "nrf91", "embassy-net", "cellular"] | ||
| 7 | categories = ["embedded", "hardware-support", "no-std", "network-programming", "asynchronous"] | ||
| 8 | license = "MIT OR Apache-2.0" | ||
| 9 | repository = "https://github.com/embassy-rs/embassy" | ||
| 10 | documentation = "https://docs.embassy.dev/embassy-net-nrf91" | ||
| 11 | |||
| 12 | [features] | ||
| 13 | defmt = [ "dep:defmt", "heapless/defmt-03" ] | ||
| 14 | log = [ "dep:log" ] | ||
| 15 | |||
| 16 | [dependencies] | ||
| 17 | defmt = { version = "0.3", optional = true } | ||
| 18 | log = { version = "0.4.14", optional = true } | ||
| 19 | |||
| 20 | nrf9160-pac = { version = "0.12.0" } | ||
| 21 | |||
| 22 | embassy-time = { version = "0.3.1", path = "../embassy-time" } | ||
| 23 | embassy-sync = { version = "0.6.0", path = "../embassy-sync"} | ||
| 24 | embassy-futures = { version = "0.1.0", path = "../embassy-futures"} | ||
| 25 | embassy-net-driver-channel = { version = "0.3.0", path = "../embassy-net-driver-channel"} | ||
| 26 | |||
| 27 | heapless = "0.8" | ||
| 28 | embedded-io = "0.6.1" | ||
| 29 | at-commands = "0.5.4" | ||
| 30 | |||
| 31 | [package.metadata.embassy_docs] | ||
| 32 | src_base = "https://github.com/embassy-rs/embassy/blob/embassy-net-nrf91-v$VERSION/embassy-net-nrf91/src/" | ||
| 33 | src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-net-nrf91/src/" | ||
| 34 | target = "thumbv7em-none-eabi" | ||
| 35 | features = ["defmt"] | ||
| 36 | |||
| 37 | [package.metadata.docs.rs] | ||
| 38 | features = ["defmt"] | ||
diff --git a/embassy-net-nrf91/README.md b/embassy-net-nrf91/README.md new file mode 100644 index 000000000..30da71787 --- /dev/null +++ b/embassy-net-nrf91/README.md | |||
| @@ -0,0 +1,9 @@ | |||
| 1 | # nRF91 `embassy-net` integration | ||
| 2 | |||
| 3 | [`embassy-net`](https://crates.io/crates/embassy-net) driver for Nordic nRF91-series cellular modems. | ||
| 4 | |||
| 5 | See the [`examples`](https://github.com/embassy-rs/embassy/tree/main/examples/nrf9160) directory for usage examples with the nRF9160. | ||
| 6 | |||
| 7 | ## Interoperability | ||
| 8 | |||
| 9 | This crate can run on any executor. | ||
diff --git a/embassy-net-nrf91/src/context.rs b/embassy-net-nrf91/src/context.rs new file mode 100644 index 000000000..8b45919ef --- /dev/null +++ b/embassy-net-nrf91/src/context.rs | |||
| @@ -0,0 +1,350 @@ | |||
| 1 | //! Helper utility to configure a specific modem context. | ||
| 2 | use core::net::IpAddr; | ||
| 3 | use core::str::FromStr; | ||
| 4 | |||
| 5 | use at_commands::builder::CommandBuilder; | ||
| 6 | use at_commands::parser::CommandParser; | ||
| 7 | use embassy_time::{Duration, Timer}; | ||
| 8 | use heapless::Vec; | ||
| 9 | |||
| 10 | /// Provides a higher level API for controlling a given context. | ||
| 11 | pub struct Control<'a> { | ||
| 12 | control: crate::Control<'a>, | ||
| 13 | cid: u8, | ||
| 14 | } | ||
| 15 | |||
| 16 | /// Configuration for a given context | ||
| 17 | pub struct Config<'a> { | ||
| 18 | /// Desired APN address. | ||
| 19 | pub apn: &'a [u8], | ||
| 20 | /// Desired authentication protocol. | ||
| 21 | pub auth_prot: AuthProt, | ||
| 22 | /// Credentials. | ||
| 23 | pub auth: Option<(&'a [u8], &'a [u8])>, | ||
| 24 | } | ||
| 25 | |||
| 26 | /// Authentication protocol. | ||
| 27 | #[derive(Clone, Copy, PartialEq, Debug)] | ||
| 28 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] | ||
| 29 | #[repr(u8)] | ||
| 30 | pub enum AuthProt { | ||
| 31 | /// No authentication. | ||
| 32 | None = 0, | ||
| 33 | /// PAP authentication. | ||
| 34 | Pap = 1, | ||
| 35 | /// CHAP authentication. | ||
| 36 | Chap = 2, | ||
| 37 | } | ||
| 38 | |||
| 39 | /// Error returned by control. | ||
| 40 | #[derive(Clone, Copy, PartialEq, Debug)] | ||
| 41 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] | ||
| 42 | pub enum Error { | ||
| 43 | /// Not enough space for command. | ||
| 44 | BufferTooSmall, | ||
| 45 | /// Error parsing response from modem. | ||
| 46 | AtParseError, | ||
| 47 | /// Error parsing IP addresses. | ||
| 48 | AddrParseError, | ||
| 49 | } | ||
| 50 | |||
| 51 | impl From<at_commands::parser::ParseError> for Error { | ||
| 52 | fn from(_: at_commands::parser::ParseError) -> Self { | ||
| 53 | Self::AtParseError | ||
| 54 | } | ||
| 55 | } | ||
| 56 | |||
| 57 | /// Status of a given context. | ||
| 58 | #[derive(PartialEq, Debug)] | ||
| 59 | pub struct Status { | ||
| 60 | /// Attached to APN or not. | ||
| 61 | pub attached: bool, | ||
| 62 | /// IP if assigned. | ||
| 63 | pub ip: Option<IpAddr>, | ||
| 64 | /// Gateway if assigned. | ||
| 65 | pub gateway: Option<IpAddr>, | ||
| 66 | /// DNS servers if assigned. | ||
| 67 | pub dns: Vec<IpAddr, 2>, | ||
| 68 | } | ||
| 69 | |||
| 70 | #[cfg(feature = "defmt")] | ||
| 71 | impl defmt::Format for Status { | ||
| 72 | fn format(&self, f: defmt::Formatter<'_>) { | ||
| 73 | defmt::write!(f, "attached: {}", self.attached); | ||
| 74 | if let Some(ip) = &self.ip { | ||
| 75 | defmt::write!(f, ", ip: {}", defmt::Debug2Format(&ip)); | ||
| 76 | } | ||
| 77 | } | ||
| 78 | } | ||
| 79 | |||
| 80 | impl<'a> Control<'a> { | ||
| 81 | /// Create a new instance of a control handle for a given context. | ||
| 82 | /// | ||
| 83 | /// Will wait for the modem to be initialized if not. | ||
| 84 | pub async fn new(control: crate::Control<'a>, cid: u8) -> Self { | ||
| 85 | control.wait_init().await; | ||
| 86 | Self { control, cid } | ||
| 87 | } | ||
| 88 | |||
| 89 | /// Perform a raw AT command | ||
| 90 | pub async fn at_command(&self, req: &[u8], resp: &mut [u8]) -> usize { | ||
| 91 | self.control.at_command(req, resp).await | ||
| 92 | } | ||
| 93 | |||
| 94 | /// Configures the modem with the provided config. | ||
| 95 | /// | ||
| 96 | /// NOTE: This will disconnect the modem from any current APN and should not | ||
| 97 | /// be called if the configuration has not been changed. | ||
| 98 | /// | ||
| 99 | /// After configuring, invoke [`enable()`] to activate the configuration. | ||
| 100 | pub async fn configure(&self, config: &Config<'_>) -> Result<(), Error> { | ||
| 101 | let mut cmd: [u8; 256] = [0; 256]; | ||
| 102 | let mut buf: [u8; 256] = [0; 256]; | ||
| 103 | |||
| 104 | let op = CommandBuilder::create_set(&mut cmd, true) | ||
| 105 | .named("+CFUN") | ||
| 106 | .with_int_parameter(0) | ||
| 107 | .finish() | ||
| 108 | .map_err(|_| Error::BufferTooSmall)?; | ||
| 109 | let n = self.control.at_command(op, &mut buf).await; | ||
| 110 | CommandParser::parse(&buf[..n]).expect_identifier(b"OK").finish()?; | ||
| 111 | |||
| 112 | let op = CommandBuilder::create_set(&mut cmd, true) | ||
| 113 | .named("+CGDCONT") | ||
| 114 | .with_int_parameter(self.cid) | ||
| 115 | .with_string_parameter("IP") | ||
| 116 | .with_string_parameter(config.apn) | ||
| 117 | .finish() | ||
| 118 | .map_err(|_| Error::BufferTooSmall)?; | ||
| 119 | let n = self.control.at_command(op, &mut buf).await; | ||
| 120 | // info!("RES1: {}", unsafe { core::str::from_utf8_unchecked(&buf[..n]) }); | ||
| 121 | CommandParser::parse(&buf[..n]).expect_identifier(b"OK").finish()?; | ||
| 122 | |||
| 123 | let mut op = CommandBuilder::create_set(&mut cmd, true) | ||
| 124 | .named("+CGAUTH") | ||
| 125 | .with_int_parameter(self.cid) | ||
| 126 | .with_int_parameter(config.auth_prot as u8); | ||
| 127 | if let Some((username, password)) = config.auth { | ||
| 128 | op = op.with_string_parameter(username).with_string_parameter(password); | ||
| 129 | } | ||
| 130 | let op = op.finish().map_err(|_| Error::BufferTooSmall)?; | ||
| 131 | |||
| 132 | let n = self.control.at_command(op, &mut buf).await; | ||
| 133 | // info!("RES2: {}", unsafe { core::str::from_utf8_unchecked(&buf[..n]) }); | ||
| 134 | CommandParser::parse(&buf[..n]).expect_identifier(b"OK").finish()?; | ||
| 135 | |||
| 136 | Ok(()) | ||
| 137 | } | ||
| 138 | |||
| 139 | /// Attach to the PDN | ||
| 140 | pub async fn attach(&self) -> Result<(), Error> { | ||
| 141 | let mut cmd: [u8; 256] = [0; 256]; | ||
| 142 | let mut buf: [u8; 256] = [0; 256]; | ||
| 143 | let op = CommandBuilder::create_set(&mut cmd, true) | ||
| 144 | .named("+CGATT") | ||
| 145 | .with_int_parameter(1) | ||
| 146 | .finish() | ||
| 147 | .map_err(|_| Error::BufferTooSmall)?; | ||
| 148 | let n = self.control.at_command(op, &mut buf).await; | ||
| 149 | CommandParser::parse(&buf[..n]).expect_identifier(b"OK").finish()?; | ||
| 150 | Ok(()) | ||
| 151 | } | ||
| 152 | |||
| 153 | /// Read current connectivity status for modem. | ||
| 154 | pub async fn detach(&self) -> Result<(), Error> { | ||
| 155 | let mut cmd: [u8; 256] = [0; 256]; | ||
| 156 | let mut buf: [u8; 256] = [0; 256]; | ||
| 157 | let op = CommandBuilder::create_set(&mut cmd, true) | ||
| 158 | .named("+CGATT") | ||
| 159 | .with_int_parameter(0) | ||
| 160 | .finish() | ||
| 161 | .map_err(|_| Error::BufferTooSmall)?; | ||
| 162 | let n = self.control.at_command(op, &mut buf).await; | ||
| 163 | CommandParser::parse(&buf[..n]).expect_identifier(b"OK").finish()?; | ||
| 164 | Ok(()) | ||
| 165 | } | ||
| 166 | |||
| 167 | async fn attached(&self) -> Result<bool, Error> { | ||
| 168 | let mut cmd: [u8; 256] = [0; 256]; | ||
| 169 | let mut buf: [u8; 256] = [0; 256]; | ||
| 170 | |||
| 171 | let op = CommandBuilder::create_query(&mut cmd, true) | ||
| 172 | .named("+CGATT") | ||
| 173 | .finish() | ||
| 174 | .map_err(|_| Error::BufferTooSmall)?; | ||
| 175 | let n = self.control.at_command(op, &mut buf).await; | ||
| 176 | let (res,) = CommandParser::parse(&buf[..n]) | ||
| 177 | .expect_identifier(b"+CGATT: ") | ||
| 178 | .expect_int_parameter() | ||
| 179 | .expect_identifier(b"\r\nOK") | ||
| 180 | .finish()?; | ||
| 181 | Ok(res == 1) | ||
| 182 | } | ||
| 183 | |||
| 184 | /// Read current connectivity status for modem. | ||
| 185 | pub async fn status(&self) -> Result<Status, Error> { | ||
| 186 | let mut cmd: [u8; 256] = [0; 256]; | ||
| 187 | let mut buf: [u8; 256] = [0; 256]; | ||
| 188 | |||
| 189 | let op = CommandBuilder::create_query(&mut cmd, true) | ||
| 190 | .named("+CGATT") | ||
| 191 | .finish() | ||
| 192 | .map_err(|_| Error::BufferTooSmall)?; | ||
| 193 | let n = self.control.at_command(op, &mut buf).await; | ||
| 194 | let (res,) = CommandParser::parse(&buf[..n]) | ||
| 195 | .expect_identifier(b"+CGATT: ") | ||
| 196 | .expect_int_parameter() | ||
| 197 | .expect_identifier(b"\r\nOK") | ||
| 198 | .finish()?; | ||
| 199 | let attached = res == 1; | ||
| 200 | if !attached { | ||
| 201 | return Ok(Status { | ||
| 202 | attached, | ||
| 203 | ip: None, | ||
| 204 | gateway: None, | ||
| 205 | dns: Vec::new(), | ||
| 206 | }); | ||
| 207 | } | ||
| 208 | |||
| 209 | let op = CommandBuilder::create_set(&mut cmd, true) | ||
| 210 | .named("+CGPADDR") | ||
| 211 | .with_int_parameter(self.cid) | ||
| 212 | .finish() | ||
| 213 | .map_err(|_| Error::BufferTooSmall)?; | ||
| 214 | let n = self.control.at_command(op, &mut buf).await; | ||
| 215 | let (_, ip1, _ip2) = CommandParser::parse(&buf[..n]) | ||
| 216 | .expect_identifier(b"+CGPADDR: ") | ||
| 217 | .expect_int_parameter() | ||
| 218 | .expect_optional_string_parameter() | ||
| 219 | .expect_optional_string_parameter() | ||
| 220 | .expect_identifier(b"\r\nOK") | ||
| 221 | .finish()?; | ||
| 222 | |||
| 223 | let ip = if let Some(ip) = ip1 { | ||
| 224 | let ip = IpAddr::from_str(ip).map_err(|_| Error::AddrParseError)?; | ||
| 225 | Some(ip) | ||
| 226 | } else { | ||
| 227 | None | ||
| 228 | }; | ||
| 229 | |||
| 230 | let op = CommandBuilder::create_set(&mut cmd, true) | ||
| 231 | .named("+CGCONTRDP") | ||
| 232 | .with_int_parameter(self.cid) | ||
| 233 | .finish() | ||
| 234 | .map_err(|_| Error::BufferTooSmall)?; | ||
| 235 | let n = self.control.at_command(op, &mut buf).await; | ||
| 236 | let (_cid, _bid, _apn, _mask, gateway, dns1, dns2, _, _, _, _, _mtu) = CommandParser::parse(&buf[..n]) | ||
| 237 | .expect_identifier(b"+CGCONTRDP: ") | ||
| 238 | .expect_int_parameter() | ||
| 239 | .expect_optional_int_parameter() | ||
| 240 | .expect_optional_string_parameter() | ||
| 241 | .expect_optional_string_parameter() | ||
| 242 | .expect_optional_string_parameter() | ||
| 243 | .expect_optional_string_parameter() | ||
| 244 | .expect_optional_string_parameter() | ||
| 245 | .expect_optional_int_parameter() | ||
| 246 | .expect_optional_int_parameter() | ||
| 247 | .expect_optional_int_parameter() | ||
| 248 | .expect_optional_int_parameter() | ||
| 249 | .expect_optional_int_parameter() | ||
| 250 | .expect_identifier(b"\r\nOK") | ||
| 251 | .finish()?; | ||
| 252 | |||
| 253 | let gateway = if let Some(ip) = gateway { | ||
| 254 | if ip.is_empty() { | ||
| 255 | None | ||
| 256 | } else { | ||
| 257 | Some(IpAddr::from_str(ip).map_err(|_| Error::AddrParseError)?) | ||
| 258 | } | ||
| 259 | } else { | ||
| 260 | None | ||
| 261 | }; | ||
| 262 | |||
| 263 | let mut dns = Vec::new(); | ||
| 264 | if let Some(ip) = dns1 { | ||
| 265 | dns.push(IpAddr::from_str(ip).map_err(|_| Error::AddrParseError)?) | ||
| 266 | .unwrap(); | ||
| 267 | } | ||
| 268 | |||
| 269 | if let Some(ip) = dns2 { | ||
| 270 | dns.push(IpAddr::from_str(ip).map_err(|_| Error::AddrParseError)?) | ||
| 271 | .unwrap(); | ||
| 272 | } | ||
| 273 | |||
| 274 | Ok(Status { | ||
| 275 | attached, | ||
| 276 | ip, | ||
| 277 | gateway, | ||
| 278 | dns, | ||
| 279 | }) | ||
| 280 | } | ||
| 281 | |||
| 282 | async fn wait_attached(&self) -> Result<Status, Error> { | ||
| 283 | while !self.attached().await? { | ||
| 284 | Timer::after(Duration::from_secs(1)).await; | ||
| 285 | } | ||
| 286 | let status = self.status().await?; | ||
| 287 | Ok(status) | ||
| 288 | } | ||
| 289 | |||
| 290 | /// Disable modem | ||
| 291 | pub async fn disable(&self) -> Result<(), Error> { | ||
| 292 | let mut cmd: [u8; 256] = [0; 256]; | ||
| 293 | let mut buf: [u8; 256] = [0; 256]; | ||
| 294 | |||
| 295 | let op = CommandBuilder::create_set(&mut cmd, true) | ||
| 296 | .named("+CFUN") | ||
| 297 | .with_int_parameter(0) | ||
| 298 | .finish() | ||
| 299 | .map_err(|_| Error::BufferTooSmall)?; | ||
| 300 | let n = self.control.at_command(op, &mut buf).await; | ||
| 301 | CommandParser::parse(&buf[..n]).expect_identifier(b"OK").finish()?; | ||
| 302 | |||
| 303 | Ok(()) | ||
| 304 | } | ||
| 305 | |||
| 306 | /// Enable modem | ||
| 307 | pub async fn enable(&self) -> Result<(), Error> { | ||
| 308 | let mut cmd: [u8; 256] = [0; 256]; | ||
| 309 | let mut buf: [u8; 256] = [0; 256]; | ||
| 310 | |||
| 311 | let op = CommandBuilder::create_set(&mut cmd, true) | ||
| 312 | .named("+CFUN") | ||
| 313 | .with_int_parameter(1) | ||
| 314 | .finish() | ||
| 315 | .map_err(|_| Error::BufferTooSmall)?; | ||
| 316 | let n = self.control.at_command(op, &mut buf).await; | ||
| 317 | CommandParser::parse(&buf[..n]).expect_identifier(b"OK").finish()?; | ||
| 318 | |||
| 319 | // Make modem survive PDN detaches | ||
| 320 | let op = CommandBuilder::create_set(&mut cmd, true) | ||
| 321 | .named("%XPDNCFG") | ||
| 322 | .with_int_parameter(1) | ||
| 323 | .finish() | ||
| 324 | .map_err(|_| Error::BufferTooSmall)?; | ||
| 325 | let n = self.control.at_command(op, &mut buf).await; | ||
| 326 | CommandParser::parse(&buf[..n]).expect_identifier(b"OK").finish()?; | ||
| 327 | Ok(()) | ||
| 328 | } | ||
| 329 | |||
| 330 | /// Run a control loop for this context, ensuring that reaattach is handled. | ||
| 331 | pub async fn run<F: Fn(&Status)>(&self, reattach: F) -> Result<(), Error> { | ||
| 332 | self.enable().await?; | ||
| 333 | let status = self.wait_attached().await?; | ||
| 334 | let mut fd = self.control.open_raw_socket().await; | ||
| 335 | reattach(&status); | ||
| 336 | |||
| 337 | loop { | ||
| 338 | if !self.attached().await? { | ||
| 339 | trace!("detached"); | ||
| 340 | |||
| 341 | self.control.close_raw_socket(fd).await; | ||
| 342 | let status = self.wait_attached().await?; | ||
| 343 | trace!("attached"); | ||
| 344 | fd = self.control.open_raw_socket().await; | ||
| 345 | reattach(&status); | ||
| 346 | } | ||
| 347 | Timer::after(Duration::from_secs(10)).await; | ||
| 348 | } | ||
| 349 | } | ||
| 350 | } | ||
diff --git a/embassy-net-nrf91/src/fmt.rs b/embassy-net-nrf91/src/fmt.rs new file mode 100644 index 000000000..35b929fde --- /dev/null +++ b/embassy-net-nrf91/src/fmt.rs | |||
| @@ -0,0 +1,274 @@ | |||
| 1 | #![macro_use] | ||
| 2 | #![allow(unused)] | ||
| 3 | |||
| 4 | use core::fmt::{Debug, Display, LowerHex}; | ||
| 5 | |||
| 6 | #[cfg(all(feature = "defmt", feature = "log"))] | ||
| 7 | compile_error!("You may not enable both `defmt` and `log` features."); | ||
| 8 | |||
| 9 | #[collapse_debuginfo(yes)] | ||
| 10 | macro_rules! assert { | ||
| 11 | ($($x:tt)*) => { | ||
| 12 | { | ||
| 13 | #[cfg(not(feature = "defmt"))] | ||
| 14 | ::core::assert!($($x)*); | ||
| 15 | #[cfg(feature = "defmt")] | ||
| 16 | ::defmt::assert!($($x)*); | ||
| 17 | } | ||
| 18 | }; | ||
| 19 | } | ||
| 20 | |||
| 21 | #[collapse_debuginfo(yes)] | ||
| 22 | macro_rules! assert_eq { | ||
| 23 | ($($x:tt)*) => { | ||
| 24 | { | ||
| 25 | #[cfg(not(feature = "defmt"))] | ||
| 26 | ::core::assert_eq!($($x)*); | ||
| 27 | #[cfg(feature = "defmt")] | ||
| 28 | ::defmt::assert_eq!($($x)*); | ||
| 29 | } | ||
| 30 | }; | ||
| 31 | } | ||
| 32 | |||
| 33 | #[collapse_debuginfo(yes)] | ||
| 34 | macro_rules! assert_ne { | ||
| 35 | ($($x:tt)*) => { | ||
| 36 | { | ||
| 37 | #[cfg(not(feature = "defmt"))] | ||
| 38 | ::core::assert_ne!($($x)*); | ||
| 39 | #[cfg(feature = "defmt")] | ||
| 40 | ::defmt::assert_ne!($($x)*); | ||
| 41 | } | ||
| 42 | }; | ||
| 43 | } | ||
| 44 | |||
| 45 | #[collapse_debuginfo(yes)] | ||
| 46 | macro_rules! debug_assert { | ||
| 47 | ($($x:tt)*) => { | ||
| 48 | { | ||
| 49 | #[cfg(not(feature = "defmt"))] | ||
| 50 | ::core::debug_assert!($($x)*); | ||
| 51 | #[cfg(feature = "defmt")] | ||
| 52 | ::defmt::debug_assert!($($x)*); | ||
| 53 | } | ||
| 54 | }; | ||
| 55 | } | ||
| 56 | |||
| 57 | #[collapse_debuginfo(yes)] | ||
| 58 | macro_rules! debug_assert_eq { | ||
| 59 | ($($x:tt)*) => { | ||
| 60 | { | ||
| 61 | #[cfg(not(feature = "defmt"))] | ||
| 62 | ::core::debug_assert_eq!($($x)*); | ||
| 63 | #[cfg(feature = "defmt")] | ||
| 64 | ::defmt::debug_assert_eq!($($x)*); | ||
| 65 | } | ||
| 66 | }; | ||
| 67 | } | ||
| 68 | |||
| 69 | #[collapse_debuginfo(yes)] | ||
| 70 | macro_rules! debug_assert_ne { | ||
| 71 | ($($x:tt)*) => { | ||
| 72 | { | ||
| 73 | #[cfg(not(feature = "defmt"))] | ||
| 74 | ::core::debug_assert_ne!($($x)*); | ||
| 75 | #[cfg(feature = "defmt")] | ||
| 76 | ::defmt::debug_assert_ne!($($x)*); | ||
| 77 | } | ||
| 78 | }; | ||
| 79 | } | ||
| 80 | |||
| 81 | #[collapse_debuginfo(yes)] | ||
| 82 | macro_rules! todo { | ||
| 83 | ($($x:tt)*) => { | ||
| 84 | { | ||
| 85 | #[cfg(not(feature = "defmt"))] | ||
| 86 | ::core::todo!($($x)*); | ||
| 87 | #[cfg(feature = "defmt")] | ||
| 88 | ::defmt::todo!($($x)*); | ||
| 89 | } | ||
| 90 | }; | ||
| 91 | } | ||
| 92 | |||
| 93 | #[cfg(not(feature = "defmt"))] | ||
| 94 | #[collapse_debuginfo(yes)] | ||
| 95 | macro_rules! unreachable { | ||
| 96 | ($($x:tt)*) => { | ||
| 97 | ::core::unreachable!($($x)*) | ||
| 98 | }; | ||
| 99 | } | ||
| 100 | |||
| 101 | #[cfg(feature = "defmt")] | ||
| 102 | #[collapse_debuginfo(yes)] | ||
| 103 | macro_rules! unreachable { | ||
| 104 | ($($x:tt)*) => { | ||
| 105 | ::defmt::unreachable!($($x)*) | ||
| 106 | }; | ||
| 107 | } | ||
| 108 | |||
| 109 | #[collapse_debuginfo(yes)] | ||
| 110 | macro_rules! panic { | ||
| 111 | ($($x:tt)*) => { | ||
| 112 | { | ||
| 113 | #[cfg(not(feature = "defmt"))] | ||
| 114 | ::core::panic!($($x)*); | ||
| 115 | #[cfg(feature = "defmt")] | ||
| 116 | ::defmt::panic!($($x)*); | ||
| 117 | } | ||
| 118 | }; | ||
| 119 | } | ||
| 120 | |||
| 121 | #[collapse_debuginfo(yes)] | ||
| 122 | macro_rules! trace { | ||
| 123 | ($s:literal $(, $x:expr)* $(,)?) => { | ||
| 124 | { | ||
| 125 | #[cfg(feature = "log")] | ||
| 126 | ::log::trace!($s $(, $x)*); | ||
| 127 | #[cfg(feature = "defmt")] | ||
| 128 | ::defmt::trace!($s $(, $x)*); | ||
| 129 | #[cfg(not(any(feature = "log", feature="defmt")))] | ||
| 130 | let _ = ($( & $x ),*); | ||
| 131 | } | ||
| 132 | }; | ||
| 133 | } | ||
| 134 | |||
| 135 | #[collapse_debuginfo(yes)] | ||
| 136 | macro_rules! debug { | ||
| 137 | ($s:literal $(, $x:expr)* $(,)?) => { | ||
| 138 | { | ||
| 139 | #[cfg(feature = "log")] | ||
| 140 | ::log::debug!($s $(, $x)*); | ||
| 141 | #[cfg(feature = "defmt")] | ||
| 142 | ::defmt::debug!($s $(, $x)*); | ||
| 143 | #[cfg(not(any(feature = "log", feature="defmt")))] | ||
| 144 | let _ = ($( & $x ),*); | ||
| 145 | } | ||
| 146 | }; | ||
| 147 | } | ||
| 148 | |||
| 149 | #[collapse_debuginfo(yes)] | ||
| 150 | macro_rules! info { | ||
| 151 | ($s:literal $(, $x:expr)* $(,)?) => { | ||
| 152 | { | ||
| 153 | #[cfg(feature = "log")] | ||
| 154 | ::log::info!($s $(, $x)*); | ||
| 155 | #[cfg(feature = "defmt")] | ||
| 156 | ::defmt::info!($s $(, $x)*); | ||
| 157 | #[cfg(not(any(feature = "log", feature="defmt")))] | ||
| 158 | let _ = ($( & $x ),*); | ||
| 159 | } | ||
| 160 | }; | ||
| 161 | } | ||
| 162 | |||
| 163 | #[collapse_debuginfo(yes)] | ||
| 164 | macro_rules! warn { | ||
| 165 | ($s:literal $(, $x:expr)* $(,)?) => { | ||
| 166 | { | ||
| 167 | #[cfg(feature = "log")] | ||
| 168 | ::log::warn!($s $(, $x)*); | ||
| 169 | #[cfg(feature = "defmt")] | ||
| 170 | ::defmt::warn!($s $(, $x)*); | ||
| 171 | #[cfg(not(any(feature = "log", feature="defmt")))] | ||
| 172 | let _ = ($( & $x ),*); | ||
| 173 | } | ||
| 174 | }; | ||
| 175 | } | ||
| 176 | |||
| 177 | #[collapse_debuginfo(yes)] | ||
| 178 | macro_rules! error { | ||
| 179 | ($s:literal $(, $x:expr)* $(,)?) => { | ||
| 180 | { | ||
| 181 | #[cfg(feature = "log")] | ||
| 182 | ::log::error!($s $(, $x)*); | ||
| 183 | #[cfg(feature = "defmt")] | ||
| 184 | ::defmt::error!($s $(, $x)*); | ||
| 185 | #[cfg(not(any(feature = "log", feature="defmt")))] | ||
| 186 | let _ = ($( & $x ),*); | ||
| 187 | } | ||
| 188 | }; | ||
| 189 | } | ||
| 190 | |||
| 191 | #[cfg(feature = "defmt")] | ||
| 192 | #[collapse_debuginfo(yes)] | ||
| 193 | macro_rules! unwrap { | ||
| 194 | ($($x:tt)*) => { | ||
| 195 | ::defmt::unwrap!($($x)*) | ||
| 196 | }; | ||
| 197 | } | ||
| 198 | |||
| 199 | #[cfg(not(feature = "defmt"))] | ||
| 200 | #[collapse_debuginfo(yes)] | ||
| 201 | macro_rules! unwrap { | ||
| 202 | ($arg:expr) => { | ||
| 203 | match $crate::fmt::Try::into_result($arg) { | ||
| 204 | ::core::result::Result::Ok(t) => t, | ||
| 205 | ::core::result::Result::Err(e) => { | ||
| 206 | ::core::panic!("unwrap of `{}` failed: {:?}", ::core::stringify!($arg), e); | ||
| 207 | } | ||
| 208 | } | ||
| 209 | }; | ||
| 210 | ($arg:expr, $($msg:expr),+ $(,)? ) => { | ||
| 211 | match $crate::fmt::Try::into_result($arg) { | ||
| 212 | ::core::result::Result::Ok(t) => t, | ||
| 213 | ::core::result::Result::Err(e) => { | ||
| 214 | ::core::panic!("unwrap of `{}` failed: {}: {:?}", ::core::stringify!($arg), ::core::format_args!($($msg,)*), e); | ||
| 215 | } | ||
| 216 | } | ||
| 217 | } | ||
| 218 | } | ||
| 219 | |||
| 220 | #[derive(Debug, Copy, Clone, Eq, PartialEq)] | ||
| 221 | pub struct NoneError; | ||
| 222 | |||
| 223 | pub trait Try { | ||
| 224 | type Ok; | ||
| 225 | type Error; | ||
| 226 | fn into_result(self) -> Result<Self::Ok, Self::Error>; | ||
| 227 | } | ||
| 228 | |||
| 229 | impl<T> Try for Option<T> { | ||
| 230 | type Ok = T; | ||
| 231 | type Error = NoneError; | ||
| 232 | |||
| 233 | #[inline] | ||
| 234 | fn into_result(self) -> Result<T, NoneError> { | ||
| 235 | self.ok_or(NoneError) | ||
| 236 | } | ||
| 237 | } | ||
| 238 | |||
| 239 | impl<T, E> Try for Result<T, E> { | ||
| 240 | type Ok = T; | ||
| 241 | type Error = E; | ||
| 242 | |||
| 243 | #[inline] | ||
| 244 | fn into_result(self) -> Self { | ||
| 245 | self | ||
| 246 | } | ||
| 247 | } | ||
| 248 | |||
| 249 | pub(crate) struct Bytes<'a>(pub &'a [u8]); | ||
| 250 | |||
| 251 | impl<'a> Debug for Bytes<'a> { | ||
| 252 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { | ||
| 253 | write!(f, "{:#02x?}", self.0) | ||
| 254 | } | ||
| 255 | } | ||
| 256 | |||
| 257 | impl<'a> Display for Bytes<'a> { | ||
| 258 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { | ||
| 259 | write!(f, "{:#02x?}", self.0) | ||
| 260 | } | ||
| 261 | } | ||
| 262 | |||
| 263 | impl<'a> LowerHex for Bytes<'a> { | ||
| 264 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { | ||
| 265 | write!(f, "{:#02x?}", self.0) | ||
| 266 | } | ||
| 267 | } | ||
| 268 | |||
| 269 | #[cfg(feature = "defmt")] | ||
| 270 | impl<'a> defmt::Format for Bytes<'a> { | ||
| 271 | fn format(&self, fmt: defmt::Formatter) { | ||
| 272 | defmt::write!(fmt, "{:02x}", self.0) | ||
| 273 | } | ||
| 274 | } | ||
diff --git a/embassy-net-nrf91/src/lib.rs b/embassy-net-nrf91/src/lib.rs new file mode 100644 index 000000000..60cdc38c6 --- /dev/null +++ b/embassy-net-nrf91/src/lib.rs | |||
| @@ -0,0 +1,1046 @@ | |||
| 1 | #![no_std] | ||
| 2 | #![doc = include_str!("../README.md")] | ||
| 3 | #![warn(missing_docs)] | ||
| 4 | #![deny(unused_must_use)] | ||
| 5 | |||
| 6 | // must be first | ||
| 7 | mod fmt; | ||
| 8 | |||
| 9 | pub mod context; | ||
| 10 | |||
| 11 | use core::cell::RefCell; | ||
| 12 | use core::future::poll_fn; | ||
| 13 | use core::marker::PhantomData; | ||
| 14 | use core::mem::{self, MaybeUninit}; | ||
| 15 | use core::ptr::{self, addr_of, addr_of_mut, copy_nonoverlapping}; | ||
| 16 | use core::slice; | ||
| 17 | use core::sync::atomic::{compiler_fence, fence, Ordering}; | ||
| 18 | use core::task::{Poll, Waker}; | ||
| 19 | |||
| 20 | use embassy_sync::blocking_mutex::raw::NoopRawMutex; | ||
| 21 | use embassy_sync::pipe; | ||
| 22 | use embassy_sync::waitqueue::{AtomicWaker, WakerRegistration}; | ||
| 23 | use heapless::Vec; | ||
| 24 | use pac::NVIC; | ||
| 25 | use {embassy_net_driver_channel as ch, nrf9160_pac as pac}; | ||
| 26 | |||
| 27 | const RX_SIZE: usize = 8 * 1024; | ||
| 28 | const TRACE_SIZE: usize = 16 * 1024; | ||
| 29 | const TRACE_BUF: usize = 1024; | ||
| 30 | const MTU: usize = 1500; | ||
| 31 | |||
| 32 | /// Network driver. | ||
| 33 | /// | ||
| 34 | /// This is the type you have to pass to `embassy-net` when creating the network stack. | ||
| 35 | pub type NetDriver<'a> = ch::Device<'a, MTU>; | ||
| 36 | |||
| 37 | static WAKER: AtomicWaker = AtomicWaker::new(); | ||
| 38 | |||
| 39 | /// Call this function on IPC IRQ | ||
| 40 | pub fn on_ipc_irq() { | ||
| 41 | let ipc = unsafe { &*pac::IPC_NS::ptr() }; | ||
| 42 | |||
| 43 | trace!("irq"); | ||
| 44 | |||
| 45 | ipc.inten.write(|w| w); | ||
| 46 | WAKER.wake(); | ||
| 47 | } | ||
| 48 | |||
| 49 | struct Allocator<'a> { | ||
| 50 | start: *mut u8, | ||
| 51 | end: *mut u8, | ||
| 52 | _phantom: PhantomData<&'a mut u8>, | ||
| 53 | } | ||
| 54 | |||
| 55 | impl<'a> Allocator<'a> { | ||
| 56 | fn alloc_bytes(&mut self, size: usize) -> &'a mut [MaybeUninit<u8>] { | ||
| 57 | // safety: both pointers come from the same allocation. | ||
| 58 | let available_size = unsafe { self.end.offset_from(self.start) } as usize; | ||
| 59 | if size > available_size { | ||
| 60 | panic!("out of memory") | ||
| 61 | } | ||
| 62 | |||
| 63 | // safety: we've checked above this doesn't go out of bounds. | ||
| 64 | let p = self.start; | ||
| 65 | self.start = unsafe { p.add(size) }; | ||
| 66 | |||
| 67 | // safety: we've checked the pointer is in-bounds. | ||
| 68 | unsafe { slice::from_raw_parts_mut(p as *mut _, size) } | ||
| 69 | } | ||
| 70 | |||
| 71 | fn alloc<T>(&mut self) -> &'a mut MaybeUninit<T> { | ||
| 72 | let align = mem::align_of::<T>(); | ||
| 73 | let size = mem::size_of::<T>(); | ||
| 74 | |||
| 75 | let align_size = match (self.start as usize) % align { | ||
| 76 | 0 => 0, | ||
| 77 | n => align - n, | ||
| 78 | }; | ||
| 79 | |||
| 80 | // safety: both pointers come from the same allocation. | ||
| 81 | let available_size = unsafe { self.end.offset_from(self.start) } as usize; | ||
| 82 | if align_size + size > available_size { | ||
| 83 | panic!("out of memory") | ||
| 84 | } | ||
| 85 | |||
| 86 | // safety: we've checked above this doesn't go out of bounds. | ||
| 87 | let p = unsafe { self.start.add(align_size) }; | ||
| 88 | self.start = unsafe { p.add(size) }; | ||
| 89 | |||
| 90 | // safety: we've checked the pointer is aligned and in-bounds. | ||
| 91 | unsafe { &mut *(p as *mut _) } | ||
| 92 | } | ||
| 93 | } | ||
| 94 | |||
| 95 | /// Create a new nRF91 embassy-net driver. | ||
| 96 | pub async fn new<'a>( | ||
| 97 | state: &'a mut State, | ||
| 98 | shmem: &'a mut [MaybeUninit<u8>], | ||
| 99 | ) -> (NetDriver<'a>, Control<'a>, Runner<'a>) { | ||
| 100 | let (n, c, r, _) = new_internal(state, shmem, None).await; | ||
| 101 | (n, c, r) | ||
| 102 | } | ||
| 103 | |||
| 104 | /// Create a new nRF91 embassy-net driver with trace. | ||
| 105 | pub async fn new_with_trace<'a>( | ||
| 106 | state: &'a mut State, | ||
| 107 | shmem: &'a mut [MaybeUninit<u8>], | ||
| 108 | trace_buffer: &'a mut TraceBuffer, | ||
| 109 | ) -> (NetDriver<'a>, Control<'a>, Runner<'a>, TraceReader<'a>) { | ||
| 110 | let (n, c, r, t) = new_internal(state, shmem, Some(trace_buffer)).await; | ||
| 111 | (n, c, r, t.unwrap()) | ||
| 112 | } | ||
| 113 | |||
| 114 | /// Create a new nRF91 embassy-net driver. | ||
| 115 | async fn new_internal<'a>( | ||
| 116 | state: &'a mut State, | ||
| 117 | shmem: &'a mut [MaybeUninit<u8>], | ||
| 118 | trace_buffer: Option<&'a mut TraceBuffer>, | ||
| 119 | ) -> (NetDriver<'a>, Control<'a>, Runner<'a>, Option<TraceReader<'a>>) { | ||
| 120 | let shmem_len = shmem.len(); | ||
| 121 | let shmem_ptr = shmem.as_mut_ptr() as *mut u8; | ||
| 122 | |||
| 123 | const SPU_REGION_SIZE: usize = 8192; // 8kb | ||
| 124 | assert!(shmem_len != 0); | ||
| 125 | assert!( | ||
| 126 | shmem_len % SPU_REGION_SIZE == 0, | ||
| 127 | "shmem length must be a multiple of 8kb" | ||
| 128 | ); | ||
| 129 | assert!( | ||
| 130 | (shmem_ptr as usize) % SPU_REGION_SIZE == 0, | ||
| 131 | "shmem length must be a multiple of 8kb" | ||
| 132 | ); | ||
| 133 | assert!( | ||
| 134 | (shmem_ptr as usize + shmem_len) < 0x2002_0000, | ||
| 135 | "shmem must be in the lower 128kb of RAM" | ||
| 136 | ); | ||
| 137 | |||
| 138 | let spu = unsafe { &*pac::SPU_S::ptr() }; | ||
| 139 | debug!("Setting IPC RAM as nonsecure..."); | ||
| 140 | let region_start = (shmem_ptr as usize - 0x2000_0000) / SPU_REGION_SIZE; | ||
| 141 | let region_end = region_start + shmem_len / SPU_REGION_SIZE; | ||
| 142 | for i in region_start..region_end { | ||
| 143 | spu.ramregion[i].perm.write(|w| { | ||
| 144 | w.execute().set_bit(); | ||
| 145 | w.write().set_bit(); | ||
| 146 | w.read().set_bit(); | ||
| 147 | w.secattr().clear_bit(); | ||
| 148 | w.lock().clear_bit(); | ||
| 149 | w | ||
| 150 | }) | ||
| 151 | } | ||
| 152 | |||
| 153 | spu.periphid[42].perm.write(|w| w.secattr().non_secure()); | ||
| 154 | |||
| 155 | let mut alloc = Allocator { | ||
| 156 | start: shmem_ptr, | ||
| 157 | end: unsafe { shmem_ptr.add(shmem_len) }, | ||
| 158 | _phantom: PhantomData, | ||
| 159 | }; | ||
| 160 | |||
| 161 | let ipc = unsafe { &*pac::IPC_NS::ptr() }; | ||
| 162 | let power = unsafe { &*pac::POWER_S::ptr() }; | ||
| 163 | |||
| 164 | let cb: &mut ControlBlock = alloc.alloc().write(unsafe { mem::zeroed() }); | ||
| 165 | let rx = alloc.alloc_bytes(RX_SIZE); | ||
| 166 | let trace = alloc.alloc_bytes(TRACE_SIZE); | ||
| 167 | |||
| 168 | cb.version = 0x00010000; | ||
| 169 | cb.rx_base = rx.as_mut_ptr() as _; | ||
| 170 | cb.rx_size = RX_SIZE; | ||
| 171 | cb.control_list_ptr = &mut cb.lists[0]; | ||
| 172 | cb.data_list_ptr = &mut cb.lists[1]; | ||
| 173 | cb.modem_info_ptr = &mut cb.modem_info; | ||
| 174 | cb.trace_ptr = &mut cb.trace; | ||
| 175 | cb.lists[0].len = LIST_LEN; | ||
| 176 | cb.lists[1].len = LIST_LEN; | ||
| 177 | cb.trace.base = trace.as_mut_ptr() as _; | ||
| 178 | cb.trace.size = TRACE_SIZE; | ||
| 179 | |||
| 180 | ipc.gpmem[0].write(|w| unsafe { w.bits(cb as *mut _ as u32) }); | ||
| 181 | ipc.gpmem[1].write(|w| unsafe { w.bits(0) }); | ||
| 182 | |||
| 183 | // connect task/event i to channel i | ||
| 184 | for i in 0..8 { | ||
| 185 | ipc.send_cnf[i].write(|w| unsafe { w.bits(1 << i) }); | ||
| 186 | ipc.receive_cnf[i].write(|w| unsafe { w.bits(1 << i) }); | ||
| 187 | } | ||
| 188 | |||
| 189 | compiler_fence(Ordering::SeqCst); | ||
| 190 | |||
| 191 | // POWER.LTEMODEM.STARTN = 0 | ||
| 192 | // The reg is missing in the PAC?? | ||
| 193 | let startn = unsafe { (power as *const _ as *mut u32).add(0x610 / 4) }; | ||
| 194 | unsafe { startn.write_volatile(0) } | ||
| 195 | |||
| 196 | unsafe { NVIC::unmask(pac::Interrupt::IPC) }; | ||
| 197 | |||
| 198 | let state_inner = &*state.inner.write(RefCell::new(StateInner { | ||
| 199 | init: false, | ||
| 200 | init_waker: WakerRegistration::new(), | ||
| 201 | cb, | ||
| 202 | requests: [const { None }; REQ_COUNT], | ||
| 203 | next_req_serial: 0x12345678, | ||
| 204 | |||
| 205 | rx_control_list: ptr::null_mut(), | ||
| 206 | rx_data_list: ptr::null_mut(), | ||
| 207 | rx_seq_no: 0, | ||
| 208 | rx_check: PointerChecker { | ||
| 209 | start: rx.as_mut_ptr() as *mut u8, | ||
| 210 | end: (rx.as_mut_ptr() as *mut u8).wrapping_add(RX_SIZE), | ||
| 211 | }, | ||
| 212 | |||
| 213 | tx_seq_no: 0, | ||
| 214 | tx_buf_used: [false; TX_BUF_COUNT], | ||
| 215 | |||
| 216 | trace_chans: Vec::new(), | ||
| 217 | trace_check: PointerChecker { | ||
| 218 | start: trace.as_mut_ptr() as *mut u8, | ||
| 219 | end: (trace.as_mut_ptr() as *mut u8).wrapping_add(TRACE_SIZE), | ||
| 220 | }, | ||
| 221 | })); | ||
| 222 | |||
| 223 | let control = Control { state: state_inner }; | ||
| 224 | |||
| 225 | let (ch_runner, device) = ch::new(&mut state.ch, ch::driver::HardwareAddress::Ip); | ||
| 226 | let state_ch = ch_runner.state_runner(); | ||
| 227 | state_ch.set_link_state(ch::driver::LinkState::Up); | ||
| 228 | |||
| 229 | let (trace_reader, trace_writer) = if let Some(trace) = trace_buffer { | ||
| 230 | let (r, w) = trace.trace.split(); | ||
| 231 | (Some(r), Some(w)) | ||
| 232 | } else { | ||
| 233 | (None, None) | ||
| 234 | }; | ||
| 235 | |||
| 236 | let runner = Runner { | ||
| 237 | ch: ch_runner, | ||
| 238 | state: state_inner, | ||
| 239 | trace_writer, | ||
| 240 | }; | ||
| 241 | |||
| 242 | (device, control, runner, trace_reader) | ||
| 243 | } | ||
| 244 | |||
| 245 | /// State holding modem traces. | ||
| 246 | pub struct TraceBuffer { | ||
| 247 | trace: pipe::Pipe<NoopRawMutex, TRACE_BUF>, | ||
| 248 | } | ||
| 249 | |||
| 250 | /// Represents writer half of the trace buffer. | ||
| 251 | pub type TraceWriter<'a> = pipe::Writer<'a, NoopRawMutex, TRACE_BUF>; | ||
| 252 | |||
| 253 | /// Represents the reader half of the trace buffer. | ||
| 254 | pub type TraceReader<'a> = pipe::Reader<'a, NoopRawMutex, TRACE_BUF>; | ||
| 255 | |||
| 256 | impl TraceBuffer { | ||
| 257 | /// Create a new TraceBuffer. | ||
| 258 | pub const fn new() -> Self { | ||
| 259 | Self { | ||
| 260 | trace: pipe::Pipe::new(), | ||
| 261 | } | ||
| 262 | } | ||
| 263 | } | ||
| 264 | |||
| 265 | /// Shared state for the driver. | ||
| 266 | pub struct State { | ||
| 267 | ch: ch::State<MTU, 4, 4>, | ||
| 268 | inner: MaybeUninit<RefCell<StateInner>>, | ||
| 269 | } | ||
| 270 | |||
| 271 | impl State { | ||
| 272 | /// Create a new State. | ||
| 273 | pub const fn new() -> Self { | ||
| 274 | Self { | ||
| 275 | ch: ch::State::new(), | ||
| 276 | inner: MaybeUninit::uninit(), | ||
| 277 | } | ||
| 278 | } | ||
| 279 | } | ||
| 280 | |||
| 281 | const TX_BUF_COUNT: usize = 4; | ||
| 282 | const TX_BUF_SIZE: usize = 1500; | ||
| 283 | |||
| 284 | struct TraceChannelInfo { | ||
| 285 | ptr: *mut TraceChannel, | ||
| 286 | start: *mut u8, | ||
| 287 | end: *mut u8, | ||
| 288 | } | ||
| 289 | |||
| 290 | const REQ_COUNT: usize = 4; | ||
| 291 | |||
| 292 | struct PendingRequest { | ||
| 293 | req_serial: u32, | ||
| 294 | resp_msg: *mut Message, | ||
| 295 | waker: Waker, | ||
| 296 | } | ||
| 297 | |||
| 298 | #[derive(Copy, Clone, PartialEq, Eq)] | ||
| 299 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] | ||
| 300 | struct NoFreeBufs; | ||
| 301 | |||
| 302 | struct StateInner { | ||
| 303 | init: bool, | ||
| 304 | init_waker: WakerRegistration, | ||
| 305 | |||
| 306 | cb: *mut ControlBlock, | ||
| 307 | requests: [Option<PendingRequest>; REQ_COUNT], | ||
| 308 | next_req_serial: u32, | ||
| 309 | |||
| 310 | rx_control_list: *mut List, | ||
| 311 | rx_data_list: *mut List, | ||
| 312 | rx_seq_no: u16, | ||
| 313 | rx_check: PointerChecker, | ||
| 314 | |||
| 315 | tx_seq_no: u16, | ||
| 316 | tx_buf_used: [bool; TX_BUF_COUNT], | ||
| 317 | |||
| 318 | trace_chans: Vec<TraceChannelInfo, TRACE_CHANNEL_COUNT>, | ||
| 319 | trace_check: PointerChecker, | ||
| 320 | } | ||
| 321 | |||
| 322 | impl StateInner { | ||
| 323 | fn poll(&mut self, trace_writer: &mut Option<TraceWriter<'_>>, ch: &mut ch::Runner<MTU>) { | ||
| 324 | trace!("poll!"); | ||
| 325 | let ipc = unsafe { &*pac::IPC_NS::ptr() }; | ||
| 326 | |||
| 327 | if ipc.events_receive[0].read().bits() != 0 { | ||
| 328 | ipc.events_receive[0].reset(); | ||
| 329 | trace!("ipc 0"); | ||
| 330 | } | ||
| 331 | |||
| 332 | if ipc.events_receive[2].read().bits() != 0 { | ||
| 333 | ipc.events_receive[2].reset(); | ||
| 334 | trace!("ipc 2"); | ||
| 335 | |||
| 336 | if !self.init { | ||
| 337 | let desc = unsafe { addr_of!((*self.cb).modem_info).read_volatile() }; | ||
| 338 | assert_eq!(desc.version, 1); | ||
| 339 | |||
| 340 | self.rx_check.check_mut(desc.control_list_ptr); | ||
| 341 | self.rx_check.check_mut(desc.data_list_ptr); | ||
| 342 | |||
| 343 | self.rx_control_list = desc.control_list_ptr; | ||
| 344 | self.rx_data_list = desc.data_list_ptr; | ||
| 345 | let rx_control_len = unsafe { addr_of!((*self.rx_control_list).len).read_volatile() }; | ||
| 346 | let rx_data_len = unsafe { addr_of!((*self.rx_data_list).len).read_volatile() }; | ||
| 347 | assert_eq!(rx_control_len, LIST_LEN); | ||
| 348 | assert_eq!(rx_data_len, LIST_LEN); | ||
| 349 | self.init = true; | ||
| 350 | |||
| 351 | debug!("IPC initialized OK!"); | ||
| 352 | self.init_waker.wake(); | ||
| 353 | } | ||
| 354 | } | ||
| 355 | |||
| 356 | if ipc.events_receive[4].read().bits() != 0 { | ||
| 357 | ipc.events_receive[4].reset(); | ||
| 358 | trace!("ipc 4"); | ||
| 359 | |||
| 360 | loop { | ||
| 361 | let list = unsafe { &mut *self.rx_control_list }; | ||
| 362 | let control_work = self.process(list, true, ch); | ||
| 363 | let list = unsafe { &mut *self.rx_data_list }; | ||
| 364 | let data_work = self.process(list, false, ch); | ||
| 365 | if !control_work && !data_work { | ||
| 366 | break; | ||
| 367 | } | ||
| 368 | } | ||
| 369 | } | ||
| 370 | |||
| 371 | if ipc.events_receive[6].read().bits() != 0 { | ||
| 372 | ipc.events_receive[6].reset(); | ||
| 373 | trace!("ipc 6"); | ||
| 374 | } | ||
| 375 | |||
| 376 | if ipc.events_receive[7].read().bits() != 0 { | ||
| 377 | ipc.events_receive[7].reset(); | ||
| 378 | trace!("ipc 7: trace"); | ||
| 379 | |||
| 380 | let msg = unsafe { addr_of!((*self.cb).trace.rx_state).read_volatile() }; | ||
| 381 | if msg != 0 { | ||
| 382 | trace!("trace msg {}", msg); | ||
| 383 | match msg { | ||
| 384 | 0 => unreachable!(), | ||
| 385 | 1 => { | ||
| 386 | let ctx = unsafe { addr_of!((*self.cb).trace.rx_ptr).read_volatile() } as *mut TraceContext; | ||
| 387 | debug!("trace init: {:?}", ctx); | ||
| 388 | self.trace_check.check(ctx); | ||
| 389 | let chans = unsafe { addr_of!((*ctx).chans).read_volatile() }; | ||
| 390 | for chan_ptr in chans { | ||
| 391 | let chan = self.trace_check.check_read(chan_ptr); | ||
| 392 | self.trace_check.check(chan.start); | ||
| 393 | self.trace_check.check(chan.end); | ||
| 394 | assert!(chan.start < chan.end); | ||
| 395 | self.trace_chans | ||
| 396 | .push(TraceChannelInfo { | ||
| 397 | ptr: chan_ptr, | ||
| 398 | start: chan.start, | ||
| 399 | end: chan.end, | ||
| 400 | }) | ||
| 401 | .map_err(|_| ()) | ||
| 402 | .unwrap() | ||
| 403 | } | ||
| 404 | } | ||
| 405 | 2 => { | ||
| 406 | for chan_info in &self.trace_chans { | ||
| 407 | let read_ptr = unsafe { addr_of!((*chan_info.ptr).read_ptr).read_volatile() }; | ||
| 408 | let write_ptr = unsafe { addr_of!((*chan_info.ptr).write_ptr).read_volatile() }; | ||
| 409 | assert!(read_ptr >= chan_info.start && read_ptr <= chan_info.end); | ||
| 410 | assert!(write_ptr >= chan_info.start && write_ptr <= chan_info.end); | ||
| 411 | if read_ptr != write_ptr { | ||
| 412 | let id = unsafe { addr_of!((*chan_info.ptr).id).read_volatile() }; | ||
| 413 | fence(Ordering::SeqCst); // synchronize volatile accesses with the slice access. | ||
| 414 | if read_ptr < write_ptr { | ||
| 415 | Self::handle_trace(trace_writer, id, unsafe { | ||
| 416 | slice::from_raw_parts(read_ptr, write_ptr.offset_from(read_ptr) as _) | ||
| 417 | }); | ||
| 418 | } else { | ||
| 419 | Self::handle_trace(trace_writer, id, unsafe { | ||
| 420 | slice::from_raw_parts(read_ptr, chan_info.end.offset_from(read_ptr) as _) | ||
| 421 | }); | ||
| 422 | Self::handle_trace(trace_writer, id, unsafe { | ||
| 423 | slice::from_raw_parts( | ||
| 424 | chan_info.start, | ||
| 425 | write_ptr.offset_from(chan_info.start) as _, | ||
| 426 | ) | ||
| 427 | }); | ||
| 428 | } | ||
| 429 | fence(Ordering::SeqCst); // synchronize volatile accesses with the slice access. | ||
| 430 | unsafe { addr_of_mut!((*chan_info.ptr).read_ptr).write_volatile(write_ptr) }; | ||
| 431 | } | ||
| 432 | } | ||
| 433 | } | ||
| 434 | _ => warn!("unknown trace msg {}", msg), | ||
| 435 | } | ||
| 436 | unsafe { addr_of_mut!((*self.cb).trace.rx_state).write_volatile(0) }; | ||
| 437 | } | ||
| 438 | } | ||
| 439 | |||
| 440 | ipc.intenset.write(|w| { | ||
| 441 | w.receive0().set_bit(); | ||
| 442 | w.receive2().set_bit(); | ||
| 443 | w.receive4().set_bit(); | ||
| 444 | w.receive6().set_bit(); | ||
| 445 | w.receive7().set_bit(); | ||
| 446 | w | ||
| 447 | }); | ||
| 448 | } | ||
| 449 | |||
| 450 | fn handle_trace(writer: &mut Option<TraceWriter<'_>>, id: u8, data: &[u8]) { | ||
| 451 | if let Some(writer) = writer { | ||
| 452 | trace!("trace: {} {}", id, data.len()); | ||
| 453 | let mut header = [0u8; 5]; | ||
| 454 | header[0] = 0xEF; | ||
| 455 | header[1] = 0xBE; | ||
| 456 | header[2..4].copy_from_slice(&(data.len() as u16).to_le_bytes()); | ||
| 457 | header[4] = id; | ||
| 458 | writer.try_write(&header).ok(); | ||
| 459 | writer.try_write(data).ok(); | ||
| 460 | } | ||
| 461 | } | ||
| 462 | |||
| 463 | fn process(&mut self, list: *mut List, is_control: bool, ch: &mut ch::Runner<MTU>) -> bool { | ||
| 464 | let mut did_work = false; | ||
| 465 | for i in 0..LIST_LEN { | ||
| 466 | let item_ptr = unsafe { addr_of_mut!((*list).items[i]) }; | ||
| 467 | let preamble = unsafe { addr_of!((*item_ptr).state).read_volatile() }; | ||
| 468 | if preamble & 0xFF == 0x01 && preamble >> 16 == self.rx_seq_no as u32 { | ||
| 469 | let msg_ptr = unsafe { addr_of!((*item_ptr).message).read_volatile() }; | ||
| 470 | let msg = self.rx_check.check_read(msg_ptr); | ||
| 471 | |||
| 472 | debug!("rx seq {} msg: {:?}", preamble >> 16, msg); | ||
| 473 | |||
| 474 | if is_control { | ||
| 475 | self.handle_control(&msg); | ||
| 476 | } else { | ||
| 477 | self.handle_data(&msg, ch); | ||
| 478 | } | ||
| 479 | |||
| 480 | unsafe { addr_of_mut!((*item_ptr).state).write_volatile(0x03) }; | ||
| 481 | self.rx_seq_no = self.rx_seq_no.wrapping_add(1); | ||
| 482 | |||
| 483 | did_work = true; | ||
| 484 | } | ||
| 485 | } | ||
| 486 | did_work | ||
| 487 | } | ||
| 488 | |||
| 489 | fn find_free_message(&mut self, ch: usize) -> Option<usize> { | ||
| 490 | for i in 0..LIST_LEN { | ||
| 491 | let preamble = unsafe { addr_of!((*self.cb).lists[ch].items[i].state).read_volatile() }; | ||
| 492 | if matches!(preamble & 0xFF, 0 | 3) { | ||
| 493 | trace!("using tx msg idx {}", i); | ||
| 494 | return Some(i); | ||
| 495 | } | ||
| 496 | } | ||
| 497 | return None; | ||
| 498 | } | ||
| 499 | |||
| 500 | fn find_free_tx_buf(&mut self) -> Option<usize> { | ||
| 501 | for i in 0..TX_BUF_COUNT { | ||
| 502 | if !self.tx_buf_used[i] { | ||
| 503 | trace!("using tx buf idx {}", i); | ||
| 504 | return Some(i); | ||
| 505 | } | ||
| 506 | } | ||
| 507 | return None; | ||
| 508 | } | ||
| 509 | |||
| 510 | fn send_message(&mut self, msg: &mut Message, data: &[u8]) -> Result<(), NoFreeBufs> { | ||
| 511 | if data.is_empty() { | ||
| 512 | msg.data = ptr::null_mut(); | ||
| 513 | msg.data_len = 0; | ||
| 514 | } else { | ||
| 515 | assert!(data.len() <= TX_BUF_SIZE); | ||
| 516 | let buf_idx = self.find_free_tx_buf().ok_or(NoFreeBufs)?; | ||
| 517 | let buf = unsafe { addr_of_mut!((*self.cb).tx_bufs[buf_idx]) } as *mut u8; | ||
| 518 | unsafe { copy_nonoverlapping(data.as_ptr(), buf, data.len()) } | ||
| 519 | msg.data = buf; | ||
| 520 | msg.data_len = data.len(); | ||
| 521 | self.tx_buf_used[buf_idx] = true; | ||
| 522 | |||
| 523 | fence(Ordering::SeqCst); // synchronize copy_nonoverlapping (non-volatile) with volatile writes below. | ||
| 524 | } | ||
| 525 | |||
| 526 | // TODO free data buf if send_message_raw fails. | ||
| 527 | self.send_message_raw(msg) | ||
| 528 | } | ||
| 529 | |||
| 530 | fn send_message_raw(&mut self, msg: &Message) -> Result<(), NoFreeBufs> { | ||
| 531 | let (ch, ipc_ch) = match msg.channel { | ||
| 532 | 1 => (0, 1), // control | ||
| 533 | 2 => (1, 3), // data | ||
| 534 | _ => unreachable!(), | ||
| 535 | }; | ||
| 536 | |||
| 537 | // allocate a msg. | ||
| 538 | let idx = self.find_free_message(ch).ok_or(NoFreeBufs)?; | ||
| 539 | |||
| 540 | debug!("tx seq {} msg: {:?}", self.tx_seq_no, msg); | ||
| 541 | |||
| 542 | let msg_slot = unsafe { addr_of_mut!((*self.cb).msgs[ch][idx]) }; | ||
| 543 | unsafe { msg_slot.write_volatile(*msg) } | ||
| 544 | let list_item = unsafe { addr_of_mut!((*self.cb).lists[ch].items[idx]) }; | ||
| 545 | unsafe { addr_of_mut!((*list_item).message).write_volatile(msg_slot) } | ||
| 546 | unsafe { addr_of_mut!((*list_item).state).write_volatile((self.tx_seq_no as u32) << 16 | 0x01) } | ||
| 547 | self.tx_seq_no = self.tx_seq_no.wrapping_add(1); | ||
| 548 | |||
| 549 | let ipc = unsafe { &*pac::IPC_NS::ptr() }; | ||
| 550 | ipc.tasks_send[ipc_ch].write(|w| unsafe { w.bits(1) }); | ||
| 551 | Ok(()) | ||
| 552 | } | ||
| 553 | |||
| 554 | fn handle_control(&mut self, msg: &Message) { | ||
| 555 | match msg.id >> 16 { | ||
| 556 | 1 => debug!("control msg: modem ready"), | ||
| 557 | 2 => self.handle_control_free(msg.data), | ||
| 558 | _ => warn!("unknown control message id {:08x}", msg.id), | ||
| 559 | } | ||
| 560 | } | ||
| 561 | |||
| 562 | fn handle_control_free(&mut self, ptr: *mut u8) { | ||
| 563 | let base = unsafe { addr_of!((*self.cb).tx_bufs) } as usize; | ||
| 564 | let ptr = ptr as usize; | ||
| 565 | |||
| 566 | if ptr < base { | ||
| 567 | warn!("control free bad pointer {:08x}", ptr); | ||
| 568 | return; | ||
| 569 | } | ||
| 570 | |||
| 571 | let diff = ptr - base; | ||
| 572 | let idx = diff / TX_BUF_SIZE; | ||
| 573 | |||
| 574 | if idx >= TX_BUF_COUNT || idx * TX_BUF_SIZE != diff { | ||
| 575 | warn!("control free bad pointer {:08x}", ptr); | ||
| 576 | return; | ||
| 577 | } | ||
| 578 | |||
| 579 | trace!("control free pointer {:08x} idx {}", ptr, idx); | ||
| 580 | if !self.tx_buf_used[idx] { | ||
| 581 | warn!( | ||
| 582 | "control free pointer {:08x} idx {}: buffer was already free??", | ||
| 583 | ptr, idx | ||
| 584 | ); | ||
| 585 | } | ||
| 586 | self.tx_buf_used[idx] = false; | ||
| 587 | } | ||
| 588 | |||
| 589 | fn handle_data(&mut self, msg: &Message, ch: &mut ch::Runner<MTU>) { | ||
| 590 | if !msg.data.is_null() { | ||
| 591 | self.rx_check.check_length(msg.data, msg.data_len); | ||
| 592 | } | ||
| 593 | |||
| 594 | let freed = match msg.id & 0xFFFF { | ||
| 595 | // AT | ||
| 596 | 3 => { | ||
| 597 | match msg.id >> 16 { | ||
| 598 | // AT request ack | ||
| 599 | 2 => false, | ||
| 600 | // AT response | ||
| 601 | 3 => self.handle_resp(msg), | ||
| 602 | // AT notification | ||
| 603 | 4 => false, | ||
| 604 | x => { | ||
| 605 | warn!("received unknown AT kind {}", x); | ||
| 606 | false | ||
| 607 | } | ||
| 608 | } | ||
| 609 | } | ||
| 610 | // IP | ||
| 611 | 4 => { | ||
| 612 | match msg.id >> 28 { | ||
| 613 | // IP response | ||
| 614 | 8 => self.handle_resp(msg), | ||
| 615 | // IP notification | ||
| 616 | 9 => match (msg.id >> 16) & 0xFFF { | ||
| 617 | // IP receive notification | ||
| 618 | 1 => { | ||
| 619 | if let Some(buf) = ch.try_rx_buf() { | ||
| 620 | let mut len = msg.data_len; | ||
| 621 | if len > buf.len() { | ||
| 622 | warn!("truncating rx'd packet from {} to {} bytes", len, buf.len()); | ||
| 623 | len = buf.len(); | ||
| 624 | } | ||
| 625 | fence(Ordering::SeqCst); // synchronize volatile accesses with the nonvolatile copy_nonoverlapping. | ||
| 626 | unsafe { ptr::copy_nonoverlapping(msg.data, buf.as_mut_ptr(), len) } | ||
| 627 | fence(Ordering::SeqCst); // synchronize volatile accesses with the nonvolatile copy_nonoverlapping. | ||
| 628 | ch.rx_done(len); | ||
| 629 | } | ||
| 630 | false | ||
| 631 | } | ||
| 632 | _ => false, | ||
| 633 | }, | ||
| 634 | x => { | ||
| 635 | warn!("received unknown IP kind {}", x); | ||
| 636 | false | ||
| 637 | } | ||
| 638 | } | ||
| 639 | } | ||
| 640 | x => { | ||
| 641 | warn!("received unknown kind {}", x); | ||
| 642 | false | ||
| 643 | } | ||
| 644 | }; | ||
| 645 | |||
| 646 | if !freed { | ||
| 647 | self.send_free(msg); | ||
| 648 | } | ||
| 649 | } | ||
| 650 | |||
| 651 | fn handle_resp(&mut self, msg: &Message) -> bool { | ||
| 652 | let req_serial = u32::from_le_bytes(msg.param[0..4].try_into().unwrap()); | ||
| 653 | if req_serial == 0 { | ||
| 654 | return false; | ||
| 655 | } | ||
| 656 | |||
| 657 | for optr in &mut self.requests { | ||
| 658 | if let Some(r) = optr { | ||
| 659 | if r.req_serial == req_serial { | ||
| 660 | let r = optr.take().unwrap(); | ||
| 661 | unsafe { r.resp_msg.write(*msg) } | ||
| 662 | r.waker.wake(); | ||
| 663 | *optr = None; | ||
| 664 | return true; | ||
| 665 | } | ||
| 666 | } | ||
| 667 | } | ||
| 668 | |||
| 669 | warn!( | ||
| 670 | "resp with id {} serial {} doesn't match any pending req", | ||
| 671 | msg.id, req_serial | ||
| 672 | ); | ||
| 673 | false | ||
| 674 | } | ||
| 675 | |||
| 676 | fn send_free(&mut self, msg: &Message) { | ||
| 677 | if msg.data.is_null() { | ||
| 678 | return; | ||
| 679 | } | ||
| 680 | |||
| 681 | let mut free_msg: Message = unsafe { mem::zeroed() }; | ||
| 682 | free_msg.channel = 1; // control | ||
| 683 | free_msg.id = 0x20001; // free | ||
| 684 | free_msg.data = msg.data; | ||
| 685 | free_msg.data_len = msg.data_len; | ||
| 686 | |||
| 687 | unwrap!(self.send_message_raw(&free_msg)); | ||
| 688 | } | ||
| 689 | } | ||
| 690 | |||
| 691 | struct PointerChecker { | ||
| 692 | start: *mut u8, | ||
| 693 | end: *mut u8, | ||
| 694 | } | ||
| 695 | |||
| 696 | impl PointerChecker { | ||
| 697 | // check the pointer is in bounds in the arena, panic otherwise. | ||
| 698 | fn check_length(&self, ptr: *const u8, len: usize) { | ||
| 699 | assert!(ptr as usize >= self.start as usize); | ||
| 700 | let end_ptr = (ptr as usize).checked_add(len).unwrap(); | ||
| 701 | assert!(end_ptr <= self.end as usize); | ||
| 702 | } | ||
| 703 | |||
| 704 | // check the pointer is in bounds in the arena, panic otherwise. | ||
| 705 | fn check<T>(&self, ptr: *const T) { | ||
| 706 | assert!(ptr.is_aligned()); | ||
| 707 | self.check_length(ptr as *const u8, mem::size_of::<T>()); | ||
| 708 | } | ||
| 709 | |||
| 710 | // check the pointer is in bounds in the arena, panic otherwise. | ||
| 711 | fn check_read<T>(&self, ptr: *const T) -> T { | ||
| 712 | self.check(ptr); | ||
| 713 | unsafe { ptr.read_volatile() } | ||
| 714 | } | ||
| 715 | |||
| 716 | // check the pointer is in bounds in the arena, panic otherwise. | ||
| 717 | fn check_mut<T>(&self, ptr: *mut T) { | ||
| 718 | self.check(ptr as *const T) | ||
| 719 | } | ||
| 720 | } | ||
| 721 | |||
| 722 | /// Control handle for the driver. | ||
| 723 | /// | ||
| 724 | /// You can use this object to control the modem at runtime, such as running AT commands. | ||
| 725 | pub struct Control<'a> { | ||
| 726 | state: &'a RefCell<StateInner>, | ||
| 727 | } | ||
| 728 | |||
| 729 | impl<'a> Control<'a> { | ||
| 730 | /// Wait for modem IPC to be initialized. | ||
| 731 | pub async fn wait_init(&self) { | ||
| 732 | poll_fn(|cx| { | ||
| 733 | let mut state = self.state.borrow_mut(); | ||
| 734 | if state.init { | ||
| 735 | return Poll::Ready(()); | ||
| 736 | } | ||
| 737 | state.init_waker.register(cx.waker()); | ||
| 738 | Poll::Pending | ||
| 739 | }) | ||
| 740 | .await | ||
| 741 | } | ||
| 742 | |||
| 743 | async fn request(&self, msg: &mut Message, req_data: &[u8], resp_data: &mut [u8]) -> usize { | ||
| 744 | // get waker | ||
| 745 | let waker = poll_fn(|cx| Poll::Ready(cx.waker().clone())).await; | ||
| 746 | |||
| 747 | // Send request | ||
| 748 | let mut state = self.state.borrow_mut(); | ||
| 749 | let mut req_serial = state.next_req_serial; | ||
| 750 | if msg.id & 0xFFFF == 3 { | ||
| 751 | // AT response seems to keep only the lower 8 bits. Others do keep the full 32 bits..?? | ||
| 752 | req_serial &= 0xFF; | ||
| 753 | } | ||
| 754 | |||
| 755 | // increment next_req_serial, skip zero because we use it as an "ignore" value. | ||
| 756 | // We have to skip when the *lowest byte* is zero because AT responses. | ||
| 757 | state.next_req_serial = state.next_req_serial.wrapping_add(1); | ||
| 758 | if state.next_req_serial & 0xFF == 0 { | ||
| 759 | state.next_req_serial = state.next_req_serial.wrapping_add(1); | ||
| 760 | } | ||
| 761 | |||
| 762 | msg.param[0..4].copy_from_slice(&req_serial.to_le_bytes()); | ||
| 763 | unwrap!(state.send_message(msg, req_data)); | ||
| 764 | |||
| 765 | // Setup the pending request state. | ||
| 766 | let (req_slot_idx, req_slot) = state | ||
| 767 | .requests | ||
| 768 | .iter_mut() | ||
| 769 | .enumerate() | ||
| 770 | .find(|(_, x)| x.is_none()) | ||
| 771 | .unwrap(); | ||
| 772 | msg.id = 0; // zero out id, so when it becomes nonzero we know the req is done. | ||
| 773 | let msg_ptr: *mut Message = msg; | ||
| 774 | *req_slot = Some(PendingRequest { | ||
| 775 | req_serial, | ||
| 776 | resp_msg: msg_ptr, | ||
| 777 | waker, | ||
| 778 | }); | ||
| 779 | |||
| 780 | drop(state); // don't borrow state across awaits. | ||
| 781 | |||
| 782 | // On cancel, unregister the request slot. | ||
| 783 | let _drop = OnDrop::new(|| { | ||
| 784 | // Remove request slot. | ||
| 785 | let mut state = self.state.borrow_mut(); | ||
| 786 | let slot = &mut state.requests[req_slot_idx]; | ||
| 787 | if let Some(s) = slot { | ||
| 788 | if s.req_serial == req_serial { | ||
| 789 | *slot = None; | ||
| 790 | } | ||
| 791 | } | ||
| 792 | |||
| 793 | // If cancelation raced with actually receiving the response, | ||
| 794 | // we own the data, so we have to free it. | ||
| 795 | let msg = unsafe { &mut *msg_ptr }; | ||
| 796 | if msg.id != 0 { | ||
| 797 | state.send_free(msg); | ||
| 798 | } | ||
| 799 | }); | ||
| 800 | // Wait for response. | ||
| 801 | poll_fn(|_| { | ||
| 802 | // we have to use the raw pointer and not the original reference `msg` | ||
| 803 | // because that'd invalidate the raw ptr that's still stored in `req_slot`. | ||
| 804 | if unsafe { (*msg_ptr).id } != 0 { | ||
| 805 | Poll::Ready(()) | ||
| 806 | } else { | ||
| 807 | Poll::Pending | ||
| 808 | } | ||
| 809 | }) | ||
| 810 | .await; | ||
| 811 | _drop.defuse(); | ||
| 812 | |||
| 813 | if msg.data.is_null() { | ||
| 814 | // no response data. | ||
| 815 | return 0; | ||
| 816 | } | ||
| 817 | |||
| 818 | // Copy response data out, if any. | ||
| 819 | // Pointer was validated in StateInner::handle_data(). | ||
| 820 | let mut len = msg.data_len; | ||
| 821 | if len > resp_data.len() { | ||
| 822 | warn!("truncating response data from {} to {}", len, resp_data.len()); | ||
| 823 | len = resp_data.len(); | ||
| 824 | } | ||
| 825 | fence(Ordering::SeqCst); // synchronize volatile accesses with the nonvolatile copy_nonoverlapping. | ||
| 826 | unsafe { ptr::copy_nonoverlapping(msg.data, resp_data.as_mut_ptr(), len) } | ||
| 827 | fence(Ordering::SeqCst); // synchronize volatile accesses with the nonvolatile copy_nonoverlapping. | ||
| 828 | self.state.borrow_mut().send_free(msg); | ||
| 829 | len | ||
| 830 | } | ||
| 831 | |||
| 832 | /// Run an AT command. | ||
| 833 | /// | ||
| 834 | /// The response is written in `resp` and its length returned. | ||
| 835 | pub async fn at_command(&self, req: &[u8], resp: &mut [u8]) -> usize { | ||
| 836 | let mut msg: Message = unsafe { mem::zeroed() }; | ||
| 837 | msg.channel = 2; // data | ||
| 838 | msg.id = 0x0001_0003; // AT command | ||
| 839 | msg.param_len = 4; | ||
| 840 | |||
| 841 | self.request(&mut msg, req, resp).await | ||
| 842 | } | ||
| 843 | |||
| 844 | /// Open the raw socket used for sending/receiving IP packets. | ||
| 845 | /// | ||
| 846 | /// This must be done after `AT+CFUN=1` (?) | ||
| 847 | async fn open_raw_socket(&self) -> u32 { | ||
| 848 | let mut msg: Message = unsafe { mem::zeroed() }; | ||
| 849 | msg.channel = 2; // data | ||
| 850 | msg.id = 0x7001_0004; // open socket | ||
| 851 | msg.param_len = 20; | ||
| 852 | |||
| 853 | let param = [ | ||
| 854 | 0xFF, 0xFF, 0xFF, 0xFF, // req_serial | ||
| 855 | 0xFF, 0xFF, 0xFF, 0xFF, // ??? | ||
| 856 | 0x05, 0x00, 0x00, 0x00, // family | ||
| 857 | 0x03, 0x00, 0x00, 0x00, // type | ||
| 858 | 0x00, 0x00, 0x00, 0x00, // protocol | ||
| 859 | ]; | ||
| 860 | msg.param[..param.len()].copy_from_slice(¶m); | ||
| 861 | |||
| 862 | self.request(&mut msg, &[], &mut []).await; | ||
| 863 | |||
| 864 | assert_eq!(msg.id, 0x80010004); | ||
| 865 | assert!(msg.param_len >= 12); | ||
| 866 | let status = u32::from_le_bytes(msg.param[8..12].try_into().unwrap()); | ||
| 867 | assert_eq!(status, 0); | ||
| 868 | assert_eq!(msg.param_len, 16); | ||
| 869 | let fd = u32::from_le_bytes(msg.param[12..16].try_into().unwrap()); | ||
| 870 | trace!("got FD: {}", fd); | ||
| 871 | fd | ||
| 872 | } | ||
| 873 | |||
| 874 | async fn close_raw_socket(&self, fd: u32) { | ||
| 875 | let mut msg: Message = unsafe { mem::zeroed() }; | ||
| 876 | msg.channel = 2; // data | ||
| 877 | msg.id = 0x7009_0004; // close socket | ||
| 878 | msg.param_len = 8; | ||
| 879 | msg.param[4..8].copy_from_slice(&fd.to_le_bytes()); | ||
| 880 | |||
| 881 | self.request(&mut msg, &[], &mut []).await; | ||
| 882 | |||
| 883 | assert_eq!(msg.id, 0x80090004); | ||
| 884 | assert!(msg.param_len >= 12); | ||
| 885 | let status = u32::from_le_bytes(msg.param[8..12].try_into().unwrap()); | ||
| 886 | assert_eq!(status, 0); | ||
| 887 | } | ||
| 888 | } | ||
| 889 | |||
| 890 | /// Background runner for the driver. | ||
| 891 | pub struct Runner<'a> { | ||
| 892 | ch: ch::Runner<'a, MTU>, | ||
| 893 | state: &'a RefCell<StateInner>, | ||
| 894 | trace_writer: Option<TraceWriter<'a>>, | ||
| 895 | } | ||
| 896 | |||
| 897 | impl<'a> Runner<'a> { | ||
| 898 | /// Run the driver operation in the background. | ||
| 899 | /// | ||
| 900 | /// You must run this in a background task, concurrently with all network operations. | ||
| 901 | pub async fn run(mut self) -> ! { | ||
| 902 | poll_fn(|cx| { | ||
| 903 | WAKER.register(cx.waker()); | ||
| 904 | |||
| 905 | let mut state = self.state.borrow_mut(); | ||
| 906 | state.poll(&mut self.trace_writer, &mut self.ch); | ||
| 907 | |||
| 908 | if let Poll::Ready(buf) = self.ch.poll_tx_buf(cx) { | ||
| 909 | let fd = 128u32; // TODO unhardcode | ||
| 910 | let mut msg: Message = unsafe { mem::zeroed() }; | ||
| 911 | msg.channel = 2; // data | ||
| 912 | msg.id = 0x7006_0004; // IP send | ||
| 913 | msg.param_len = 12; | ||
| 914 | msg.param[4..8].copy_from_slice(&fd.to_le_bytes()); | ||
| 915 | if let Err(e) = state.send_message(&mut msg, buf) { | ||
| 916 | warn!("tx failed: {:?}", e); | ||
| 917 | } | ||
| 918 | self.ch.tx_done(); | ||
| 919 | } | ||
| 920 | |||
| 921 | Poll::Pending | ||
| 922 | }) | ||
| 923 | .await | ||
| 924 | } | ||
| 925 | } | ||
| 926 | |||
| 927 | const LIST_LEN: usize = 16; | ||
| 928 | |||
| 929 | #[repr(C)] | ||
| 930 | struct ControlBlock { | ||
| 931 | version: u32, | ||
| 932 | rx_base: *mut u8, | ||
| 933 | rx_size: usize, | ||
| 934 | control_list_ptr: *mut List, | ||
| 935 | data_list_ptr: *mut List, | ||
| 936 | modem_info_ptr: *mut ModemInfo, | ||
| 937 | trace_ptr: *mut Trace, | ||
| 938 | unk: u32, | ||
| 939 | |||
| 940 | modem_info: ModemInfo, | ||
| 941 | trace: Trace, | ||
| 942 | |||
| 943 | // 0 = control, 1 = data | ||
| 944 | lists: [List; 2], | ||
| 945 | msgs: [[Message; LIST_LEN]; 2], | ||
| 946 | |||
| 947 | tx_bufs: [[u8; TX_BUF_SIZE]; TX_BUF_COUNT], | ||
| 948 | } | ||
| 949 | |||
| 950 | #[repr(C)] | ||
| 951 | struct ModemInfo { | ||
| 952 | version: u32, | ||
| 953 | control_list_ptr: *mut List, | ||
| 954 | data_list_ptr: *mut List, | ||
| 955 | padding: [u32; 5], | ||
| 956 | } | ||
| 957 | |||
| 958 | #[repr(C)] | ||
| 959 | struct Trace { | ||
| 960 | size: usize, | ||
| 961 | base: *mut u8, | ||
| 962 | tx_state: u32, | ||
| 963 | tx_ptr: *mut u8, | ||
| 964 | rx_state: u32, | ||
| 965 | rx_ptr: *mut u8, | ||
| 966 | unk1: u32, | ||
| 967 | unk2: u32, | ||
| 968 | } | ||
| 969 | |||
| 970 | const TRACE_CHANNEL_COUNT: usize = 3; | ||
| 971 | |||
| 972 | #[repr(C)] | ||
| 973 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] | ||
| 974 | struct TraceContext { | ||
| 975 | unk1: u32, | ||
| 976 | unk2: u32, | ||
| 977 | len: u32, | ||
| 978 | chans: [*mut TraceChannel; TRACE_CHANNEL_COUNT], | ||
| 979 | } | ||
| 980 | |||
| 981 | #[repr(C)] | ||
| 982 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] | ||
| 983 | struct TraceChannel { | ||
| 984 | id: u8, | ||
| 985 | unk1: u8, | ||
| 986 | unk2: u8, | ||
| 987 | unk3: u8, | ||
| 988 | write_ptr: *mut u8, | ||
| 989 | read_ptr: *mut u8, | ||
| 990 | start: *mut u8, | ||
| 991 | end: *mut u8, | ||
| 992 | } | ||
| 993 | |||
| 994 | #[repr(C)] | ||
| 995 | struct List { | ||
| 996 | len: usize, | ||
| 997 | items: [ListItem; LIST_LEN], | ||
| 998 | } | ||
| 999 | |||
| 1000 | #[repr(C)] | ||
| 1001 | struct ListItem { | ||
| 1002 | /// top 16 bits: seqno | ||
| 1003 | /// bottom 8 bits: | ||
| 1004 | /// 0x01: sent | ||
| 1005 | /// 0x02: held | ||
| 1006 | /// 0x03: freed | ||
| 1007 | state: u32, | ||
| 1008 | message: *mut Message, | ||
| 1009 | } | ||
| 1010 | |||
| 1011 | #[repr(C)] | ||
| 1012 | #[derive(defmt::Format, Clone, Copy)] | ||
| 1013 | struct Message { | ||
| 1014 | id: u32, | ||
| 1015 | |||
| 1016 | /// 1 = control, 2 = data | ||
| 1017 | channel: u8, | ||
| 1018 | unk1: u8, | ||
| 1019 | unk2: u8, | ||
| 1020 | unk3: u8, | ||
| 1021 | |||
| 1022 | data: *mut u8, | ||
| 1023 | data_len: usize, | ||
| 1024 | param_len: usize, | ||
| 1025 | param: [u8; 44], | ||
| 1026 | } | ||
| 1027 | |||
| 1028 | struct OnDrop<F: FnOnce()> { | ||
| 1029 | f: MaybeUninit<F>, | ||
| 1030 | } | ||
| 1031 | |||
| 1032 | impl<F: FnOnce()> OnDrop<F> { | ||
| 1033 | pub fn new(f: F) -> Self { | ||
| 1034 | Self { f: MaybeUninit::new(f) } | ||
| 1035 | } | ||
| 1036 | |||
| 1037 | pub fn defuse(self) { | ||
| 1038 | mem::forget(self) | ||
| 1039 | } | ||
| 1040 | } | ||
| 1041 | |||
| 1042 | impl<F: FnOnce()> Drop for OnDrop<F> { | ||
| 1043 | fn drop(&mut self) { | ||
| 1044 | unsafe { self.f.as_ptr().read()() } | ||
| 1045 | } | ||
| 1046 | } | ||
diff --git a/embassy-nrf/src/buffered_uarte.rs b/embassy-nrf/src/buffered_uarte.rs index 159b4db8f..6d39597c6 100644 --- a/embassy-nrf/src/buffered_uarte.rs +++ b/embassy-nrf/src/buffered_uarte.rs | |||
| @@ -358,6 +358,11 @@ impl<'d, U: UarteInstance, T: TimerInstance> BufferedUarte<'d, U, T> { | |||
| 358 | self.tx.write(buf).await | 358 | self.tx.write(buf).await |
| 359 | } | 359 | } |
| 360 | 360 | ||
| 361 | /// Try writing a buffer without waiting, returning how many bytes were written. | ||
| 362 | pub fn try_write(&mut self, buf: &[u8]) -> Result<usize, Error> { | ||
| 363 | self.tx.try_write(buf) | ||
| 364 | } | ||
| 365 | |||
| 361 | /// Flush this output stream, ensuring that all intermediately buffered contents reach their destination. | 366 | /// Flush this output stream, ensuring that all intermediately buffered contents reach their destination. |
| 362 | pub async fn flush(&mut self) -> Result<(), Error> { | 367 | pub async fn flush(&mut self) -> Result<(), Error> { |
| 363 | self.tx.flush().await | 368 | self.tx.flush().await |
| @@ -482,6 +487,29 @@ impl<'d, U: UarteInstance> BufferedUarteTx<'d, U> { | |||
| 482 | .await | 487 | .await |
| 483 | } | 488 | } |
| 484 | 489 | ||
| 490 | /// Try writing a buffer without waiting, returning how many bytes were written. | ||
| 491 | pub fn try_write(&mut self, buf: &[u8]) -> Result<usize, Error> { | ||
| 492 | //trace!("poll_write: {:?}", buf.len()); | ||
| 493 | let s = U::buffered_state(); | ||
| 494 | let mut tx = unsafe { s.tx_buf.writer() }; | ||
| 495 | |||
| 496 | let tx_buf = tx.push_slice(); | ||
| 497 | if tx_buf.is_empty() { | ||
| 498 | return Ok(0); | ||
| 499 | } | ||
| 500 | |||
| 501 | let n = min(tx_buf.len(), buf.len()); | ||
| 502 | tx_buf[..n].copy_from_slice(&buf[..n]); | ||
| 503 | tx.push_done(n); | ||
| 504 | |||
| 505 | //trace!("poll_write: queued {:?}", n); | ||
| 506 | |||
| 507 | compiler_fence(Ordering::SeqCst); | ||
| 508 | U::Interrupt::pend(); | ||
| 509 | |||
| 510 | Ok(n) | ||
| 511 | } | ||
| 512 | |||
| 485 | /// Flush this output stream, ensuring that all intermediately buffered contents reach their destination. | 513 | /// Flush this output stream, ensuring that all intermediately buffered contents reach their destination. |
| 486 | pub async fn flush(&mut self) -> Result<(), Error> { | 514 | pub async fn flush(&mut self) -> Result<(), Error> { |
| 487 | poll_fn(move |cx| { | 515 | poll_fn(move |cx| { |
diff --git a/examples/nrf9160/.cargo/config.toml b/examples/nrf9160/.cargo/config.toml index f64c63966..6072b8595 100644 --- a/examples/nrf9160/.cargo/config.toml +++ b/examples/nrf9160/.cargo/config.toml | |||
| @@ -1,5 +1,6 @@ | |||
| 1 | [target.'cfg(all(target_arch = "arm", target_os = "none"))'] | 1 | [target.'cfg(all(target_arch = "arm", target_os = "none"))'] |
| 2 | runner = "probe-rs run --chip nRF9160_xxAA" | 2 | # runner = "probe-rs run --chip nRF9160_xxAA" |
| 3 | runner = [ "probe-rs", "run", "--chip=nRF9160_xxAA", "--always-print-stacktrace", "--log-format={t} {[{L}]%bold} {s} {{c} {ff}:{l:1}%dimmed}" ] | ||
| 3 | 4 | ||
| 4 | [build] | 5 | [build] |
| 5 | target = "thumbv8m.main-none-eabihf" | 6 | target = "thumbv8m.main-none-eabihf" |
diff --git a/examples/nrf9160/Cargo.toml b/examples/nrf9160/Cargo.toml index c30b54ebd..9aeb99317 100644 --- a/examples/nrf9160/Cargo.toml +++ b/examples/nrf9160/Cargo.toml | |||
| @@ -8,13 +8,19 @@ license = "MIT OR Apache-2.0" | |||
| 8 | embassy-executor = { version = "0.6.0", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] } | 8 | embassy-executor = { version = "0.6.0", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] } |
| 9 | embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime"] } | 9 | embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime"] } |
| 10 | embassy-nrf = { version = "0.2.0", path = "../../embassy-nrf", features = ["defmt", "nrf9160-s", "time-driver-rtc1", "gpiote", "unstable-pac", "time"] } | 10 | embassy-nrf = { version = "0.2.0", path = "../../embassy-nrf", features = ["defmt", "nrf9160-s", "time-driver-rtc1", "gpiote", "unstable-pac", "time"] } |
| 11 | embassy-net-nrf91 = { version = "0.1.0", path = "../../embassy-net-nrf91", features = ["defmt"] } | ||
| 12 | embassy-net = { version = "0.4.0", path = "../../embassy-net", features = ["defmt", "tcp", "proto-ipv4", "medium-ip"] } | ||
| 11 | 13 | ||
| 12 | defmt = "0.3" | 14 | defmt = "0.3" |
| 13 | defmt-rtt = "0.4" | 15 | defmt-rtt = "0.4" |
| 14 | 16 | ||
| 17 | heapless = "0.8" | ||
| 15 | cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } | 18 | cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } |
| 16 | cortex-m-rt = "0.7.0" | 19 | cortex-m-rt = "0.7.0" |
| 17 | panic-probe = { version = "0.3", features = ["print-defmt"] } | 20 | panic-probe = { version = "0.3", features = ["print-defmt"] } |
| 21 | static_cell = { version = "2" } | ||
| 22 | embedded-io = "0.6.1" | ||
| 23 | embedded-io-async = { version = "0.6.1", features = ["defmt-03"] } | ||
| 18 | 24 | ||
| 19 | [profile.release] | 25 | [profile.release] |
| 20 | debug = 2 | 26 | debug = 2 |
diff --git a/examples/nrf9160/memory.x b/examples/nrf9160/memory.x index 4c7d4ebf0..e33498773 100644 --- a/examples/nrf9160/memory.x +++ b/examples/nrf9160/memory.x | |||
| @@ -1,5 +1,9 @@ | |||
| 1 | MEMORY | 1 | MEMORY |
| 2 | { | 2 | { |
| 3 | FLASH : ORIGIN = 0x00000000, LENGTH = 1024K | 3 | FLASH : ORIGIN = 0x00000000, LENGTH = 1024K |
| 4 | RAM : ORIGIN = 0x20018000, LENGTH = 160K | 4 | RAM : ORIGIN = 0x20010000, LENGTH = 192K |
| 5 | IPC : ORIGIN = 0x20000000, LENGTH = 64K | ||
| 5 | } | 6 | } |
| 7 | |||
| 8 | PROVIDE(__start_ipc = ORIGIN(IPC)); | ||
| 9 | PROVIDE(__end_ipc = ORIGIN(IPC) + LENGTH(IPC)); | ||
diff --git a/examples/nrf9160/src/bin/modem_tcp_client.rs b/examples/nrf9160/src/bin/modem_tcp_client.rs new file mode 100644 index 000000000..5335b6b51 --- /dev/null +++ b/examples/nrf9160/src/bin/modem_tcp_client.rs | |||
| @@ -0,0 +1,204 @@ | |||
| 1 | #![no_std] | ||
| 2 | #![no_main] | ||
| 3 | |||
| 4 | use core::mem::MaybeUninit; | ||
| 5 | use core::net::IpAddr; | ||
| 6 | use core::ptr::addr_of_mut; | ||
| 7 | use core::slice; | ||
| 8 | use core::str::FromStr; | ||
| 9 | |||
| 10 | use defmt::{info, unwrap, warn}; | ||
| 11 | use embassy_executor::Spawner; | ||
| 12 | use embassy_net::{Ipv4Address, Ipv4Cidr, Stack, StackResources}; | ||
| 13 | use embassy_net_nrf91::context::Status; | ||
| 14 | use embassy_net_nrf91::{context, Runner, State, TraceBuffer, TraceReader}; | ||
| 15 | use embassy_nrf::buffered_uarte::{self, BufferedUarteTx}; | ||
| 16 | use embassy_nrf::gpio::{AnyPin, Level, Output, OutputDrive, Pin}; | ||
| 17 | use embassy_nrf::uarte::Baudrate; | ||
| 18 | use embassy_nrf::{bind_interrupts, interrupt, peripherals, uarte}; | ||
| 19 | use embassy_time::{Duration, Timer}; | ||
| 20 | use embedded_io_async::Write; | ||
| 21 | use heapless::Vec; | ||
| 22 | use static_cell::StaticCell; | ||
| 23 | use {defmt_rtt as _, panic_probe as _}; | ||
| 24 | |||
| 25 | #[interrupt] | ||
| 26 | fn IPC() { | ||
| 27 | embassy_net_nrf91::on_ipc_irq(); | ||
| 28 | } | ||
| 29 | |||
| 30 | bind_interrupts!(struct Irqs { | ||
| 31 | UARTE0_SPIM0_SPIS0_TWIM0_TWIS0 => buffered_uarte::InterruptHandler<peripherals::SERIAL0>; | ||
| 32 | }); | ||
| 33 | |||
| 34 | #[embassy_executor::task] | ||
| 35 | async fn trace_task(mut uart: BufferedUarteTx<'static, peripherals::SERIAL0>, reader: TraceReader<'static>) -> ! { | ||
| 36 | let mut rx = [0u8; 1024]; | ||
| 37 | loop { | ||
| 38 | let n = reader.read(&mut rx[..]).await; | ||
| 39 | unwrap!(uart.write_all(&rx[..n]).await); | ||
| 40 | } | ||
| 41 | } | ||
| 42 | |||
| 43 | #[embassy_executor::task] | ||
| 44 | async fn modem_task(runner: Runner<'static>) -> ! { | ||
| 45 | runner.run().await | ||
| 46 | } | ||
| 47 | |||
| 48 | #[embassy_executor::task] | ||
| 49 | async fn net_task(stack: &'static Stack<embassy_net_nrf91::NetDriver<'static>>) -> ! { | ||
| 50 | stack.run().await | ||
| 51 | } | ||
| 52 | |||
| 53 | #[embassy_executor::task] | ||
| 54 | async fn control_task( | ||
| 55 | control: &'static context::Control<'static>, | ||
| 56 | config: context::Config<'static>, | ||
| 57 | stack: &'static Stack<embassy_net_nrf91::NetDriver<'static>>, | ||
| 58 | ) { | ||
| 59 | unwrap!(control.configure(&config).await); | ||
| 60 | unwrap!( | ||
| 61 | control | ||
| 62 | .run(|status| { | ||
| 63 | stack.set_config_v4(status_to_config(status)); | ||
| 64 | }) | ||
| 65 | .await | ||
| 66 | ); | ||
| 67 | } | ||
| 68 | |||
| 69 | fn status_to_config(status: &Status) -> embassy_net::ConfigV4 { | ||
| 70 | let Some(IpAddr::V4(addr)) = status.ip else { | ||
| 71 | panic!("Unexpected IP address"); | ||
| 72 | }; | ||
| 73 | let addr = Ipv4Address(addr.octets()); | ||
| 74 | |||
| 75 | let gateway = if let Some(IpAddr::V4(addr)) = status.gateway { | ||
| 76 | Some(Ipv4Address(addr.octets())) | ||
| 77 | } else { | ||
| 78 | None | ||
| 79 | }; | ||
| 80 | |||
| 81 | let mut dns_servers = Vec::new(); | ||
| 82 | for dns in status.dns.iter() { | ||
| 83 | if let IpAddr::V4(ip) = dns { | ||
| 84 | unwrap!(dns_servers.push(Ipv4Address(ip.octets()))); | ||
| 85 | } | ||
| 86 | } | ||
| 87 | |||
| 88 | embassy_net::ConfigV4::Static(embassy_net::StaticConfigV4 { | ||
| 89 | address: Ipv4Cidr::new(addr, 32), | ||
| 90 | gateway, | ||
| 91 | dns_servers, | ||
| 92 | }) | ||
| 93 | } | ||
| 94 | |||
| 95 | #[embassy_executor::task] | ||
| 96 | async fn blink_task(pin: AnyPin) { | ||
| 97 | let mut led = Output::new(pin, Level::Low, OutputDrive::Standard); | ||
| 98 | loop { | ||
| 99 | led.set_high(); | ||
| 100 | Timer::after_millis(1000).await; | ||
| 101 | led.set_low(); | ||
| 102 | Timer::after_millis(1000).await; | ||
| 103 | } | ||
| 104 | } | ||
| 105 | |||
| 106 | extern "C" { | ||
| 107 | static __start_ipc: u8; | ||
| 108 | static __end_ipc: u8; | ||
| 109 | } | ||
| 110 | |||
| 111 | #[embassy_executor::main] | ||
| 112 | async fn main(spawner: Spawner) { | ||
| 113 | let p = embassy_nrf::init(Default::default()); | ||
| 114 | |||
| 115 | info!("Hello World!"); | ||
| 116 | |||
| 117 | unwrap!(spawner.spawn(blink_task(p.P0_02.degrade()))); | ||
| 118 | |||
| 119 | let ipc_mem = unsafe { | ||
| 120 | let ipc_start = &__start_ipc as *const u8 as *mut MaybeUninit<u8>; | ||
| 121 | let ipc_end = &__end_ipc as *const u8 as *mut MaybeUninit<u8>; | ||
| 122 | let ipc_len = ipc_end.offset_from(ipc_start) as usize; | ||
| 123 | slice::from_raw_parts_mut(ipc_start, ipc_len) | ||
| 124 | }; | ||
| 125 | |||
| 126 | static mut TRACE_BUF: [u8; 4096] = [0u8; 4096]; | ||
| 127 | let mut config = uarte::Config::default(); | ||
| 128 | config.baudrate = Baudrate::BAUD1M; | ||
| 129 | let uart = BufferedUarteTx::new( | ||
| 130 | //let trace_uart = BufferedUarteTx::new( | ||
| 131 | unsafe { peripherals::SERIAL0::steal() }, | ||
| 132 | Irqs, | ||
| 133 | unsafe { peripherals::P0_01::steal() }, | ||
| 134 | //unsafe { peripherals::P0_14::steal() }, | ||
| 135 | config, | ||
| 136 | unsafe { &mut *addr_of_mut!(TRACE_BUF) }, | ||
| 137 | ); | ||
| 138 | |||
| 139 | static STATE: StaticCell<State> = StaticCell::new(); | ||
| 140 | static TRACE: StaticCell<TraceBuffer> = StaticCell::new(); | ||
| 141 | let (device, control, runner, tracer) = | ||
| 142 | embassy_net_nrf91::new_with_trace(STATE.init(State::new()), ipc_mem, TRACE.init(TraceBuffer::new())).await; | ||
| 143 | unwrap!(spawner.spawn(modem_task(runner))); | ||
| 144 | unwrap!(spawner.spawn(trace_task(uart, tracer))); | ||
| 145 | |||
| 146 | let config = embassy_net::Config::default(); | ||
| 147 | |||
| 148 | // Generate "random" seed. nRF91 has no RNG, TODO figure out something... | ||
| 149 | let seed = 123456; | ||
| 150 | |||
| 151 | // Init network stack | ||
| 152 | static RESOURCES: StaticCell<StackResources<2>> = StaticCell::new(); | ||
| 153 | static STACK: StaticCell<Stack<embassy_net_nrf91::NetDriver<'static>>> = StaticCell::new(); | ||
| 154 | let stack = &*STACK.init(Stack::new( | ||
| 155 | device, | ||
| 156 | config, | ||
| 157 | RESOURCES.init(StackResources::<2>::new()), | ||
| 158 | seed, | ||
| 159 | )); | ||
| 160 | |||
| 161 | unwrap!(spawner.spawn(net_task(stack))); | ||
| 162 | |||
| 163 | static CONTROL: StaticCell<context::Control<'static>> = StaticCell::new(); | ||
| 164 | let control = CONTROL.init(context::Control::new(control, 0).await); | ||
| 165 | |||
| 166 | unwrap!(spawner.spawn(control_task( | ||
| 167 | control, | ||
| 168 | context::Config { | ||
| 169 | apn: b"iot.nat.es", | ||
| 170 | auth_prot: context::AuthProt::Pap, | ||
| 171 | auth: Some((b"orange", b"orange")), | ||
| 172 | }, | ||
| 173 | stack | ||
| 174 | ))); | ||
| 175 | |||
| 176 | stack.wait_config_up().await; | ||
| 177 | |||
| 178 | let mut rx_buffer = [0; 4096]; | ||
| 179 | let mut tx_buffer = [0; 4096]; | ||
| 180 | loop { | ||
| 181 | let mut socket = embassy_net::tcp::TcpSocket::new(stack, &mut rx_buffer, &mut tx_buffer); | ||
| 182 | socket.set_timeout(Some(Duration::from_secs(10))); | ||
| 183 | |||
| 184 | info!("Connecting..."); | ||
| 185 | let host_addr = embassy_net::Ipv4Address::from_str("45.79.112.203").unwrap(); | ||
| 186 | if let Err(e) = socket.connect((host_addr, 4242)).await { | ||
| 187 | warn!("connect error: {:?}", e); | ||
| 188 | Timer::after_secs(10).await; | ||
| 189 | continue; | ||
| 190 | } | ||
| 191 | info!("Connected to {:?}", socket.remote_endpoint()); | ||
| 192 | |||
| 193 | let msg = b"Hello world!\n"; | ||
| 194 | for _ in 0..10 { | ||
| 195 | if let Err(e) = socket.write_all(msg).await { | ||
| 196 | warn!("write error: {:?}", e); | ||
| 197 | break; | ||
| 198 | } | ||
| 199 | info!("txd: {}", core::str::from_utf8(msg).unwrap()); | ||
| 200 | Timer::after_secs(1).await; | ||
| 201 | } | ||
| 202 | Timer::after_secs(4).await; | ||
| 203 | } | ||
| 204 | } | ||
