aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDario Nieuwenhuis <[email protected]>2024-09-09 20:03:28 +0000
committerGitHub <[email protected]>2024-09-09 20:03:28 +0000
commit2286e5da13b11f0cfc37e5345e3ed3c40f774055 (patch)
tree5afa31997e3dd024135151ce64813f5db478569b
parent0ef06cc19b61c8196fea941514ec313a0f15d145 (diff)
parent6af1cb7a20fe15750b353e2e6af6832786f8c065 (diff)
Merge pull request #3105 from embassy-rs/net-nrf91
embassy-net driver for nrf91
-rw-r--r--embassy-net-nrf91/Cargo.toml38
-rw-r--r--embassy-net-nrf91/README.md9
-rw-r--r--embassy-net-nrf91/src/context.rs350
-rw-r--r--embassy-net-nrf91/src/fmt.rs274
-rw-r--r--embassy-net-nrf91/src/lib.rs1046
-rw-r--r--embassy-nrf/src/buffered_uarte.rs28
-rw-r--r--examples/nrf9160/.cargo/config.toml3
-rw-r--r--examples/nrf9160/Cargo.toml6
-rw-r--r--examples/nrf9160/memory.x8
-rw-r--r--examples/nrf9160/src/bin/modem_tcp_client.rs204
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]
2name = "embassy-net-nrf91"
3version = "0.1.0"
4edition = "2021"
5description = "embassy-net driver for Nordic nRF91-series cellular modems"
6keywords = ["embedded", "nrf91", "embassy-net", "cellular"]
7categories = ["embedded", "hardware-support", "no-std", "network-programming", "asynchronous"]
8license = "MIT OR Apache-2.0"
9repository = "https://github.com/embassy-rs/embassy"
10documentation = "https://docs.embassy.dev/embassy-net-nrf91"
11
12[features]
13defmt = [ "dep:defmt", "heapless/defmt-03" ]
14log = [ "dep:log" ]
15
16[dependencies]
17defmt = { version = "0.3", optional = true }
18log = { version = "0.4.14", optional = true }
19
20nrf9160-pac = { version = "0.12.0" }
21
22embassy-time = { version = "0.3.1", path = "../embassy-time" }
23embassy-sync = { version = "0.6.0", path = "../embassy-sync"}
24embassy-futures = { version = "0.1.0", path = "../embassy-futures"}
25embassy-net-driver-channel = { version = "0.3.0", path = "../embassy-net-driver-channel"}
26
27heapless = "0.8"
28embedded-io = "0.6.1"
29at-commands = "0.5.4"
30
31[package.metadata.embassy_docs]
32src_base = "https://github.com/embassy-rs/embassy/blob/embassy-net-nrf91-v$VERSION/embassy-net-nrf91/src/"
33src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-net-nrf91/src/"
34target = "thumbv7em-none-eabi"
35features = ["defmt"]
36
37[package.metadata.docs.rs]
38features = ["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
5See the [`examples`](https://github.com/embassy-rs/embassy/tree/main/examples/nrf9160) directory for usage examples with the nRF9160.
6
7## Interoperability
8
9This 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.
2use core::net::IpAddr;
3use core::str::FromStr;
4
5use at_commands::builder::CommandBuilder;
6use at_commands::parser::CommandParser;
7use embassy_time::{Duration, Timer};
8use heapless::Vec;
9
10/// Provides a higher level API for controlling a given context.
11pub struct Control<'a> {
12 control: crate::Control<'a>,
13 cid: u8,
14}
15
16/// Configuration for a given context
17pub 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)]
30pub 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))]
42pub 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
51impl 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)]
59pub 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")]
71impl 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
80impl<'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
4use core::fmt::{Debug, Display, LowerHex};
5
6#[cfg(all(feature = "defmt", feature = "log"))]
7compile_error!("You may not enable both `defmt` and `log` features.");
8
9#[collapse_debuginfo(yes)]
10macro_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)]
22macro_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)]
34macro_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)]
46macro_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)]
58macro_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)]
70macro_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)]
82macro_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)]
95macro_rules! unreachable {
96 ($($x:tt)*) => {
97 ::core::unreachable!($($x)*)
98 };
99}
100
101#[cfg(feature = "defmt")]
102#[collapse_debuginfo(yes)]
103macro_rules! unreachable {
104 ($($x:tt)*) => {
105 ::defmt::unreachable!($($x)*)
106 };
107}
108
109#[collapse_debuginfo(yes)]
110macro_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)]
122macro_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)]
136macro_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)]
150macro_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)]
164macro_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)]
178macro_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)]
193macro_rules! unwrap {
194 ($($x:tt)*) => {
195 ::defmt::unwrap!($($x)*)
196 };
197}
198
199#[cfg(not(feature = "defmt"))]
200#[collapse_debuginfo(yes)]
201macro_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)]
221pub struct NoneError;
222
223pub trait Try {
224 type Ok;
225 type Error;
226 fn into_result(self) -> Result<Self::Ok, Self::Error>;
227}
228
229impl<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
239impl<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
249pub(crate) struct Bytes<'a>(pub &'a [u8]);
250
251impl<'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
257impl<'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
263impl<'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")]
270impl<'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
7mod fmt;
8
9pub mod context;
10
11use core::cell::RefCell;
12use core::future::poll_fn;
13use core::marker::PhantomData;
14use core::mem::{self, MaybeUninit};
15use core::ptr::{self, addr_of, addr_of_mut, copy_nonoverlapping};
16use core::slice;
17use core::sync::atomic::{compiler_fence, fence, Ordering};
18use core::task::{Poll, Waker};
19
20use embassy_sync::blocking_mutex::raw::NoopRawMutex;
21use embassy_sync::pipe;
22use embassy_sync::waitqueue::{AtomicWaker, WakerRegistration};
23use heapless::Vec;
24use pac::NVIC;
25use {embassy_net_driver_channel as ch, nrf9160_pac as pac};
26
27const RX_SIZE: usize = 8 * 1024;
28const TRACE_SIZE: usize = 16 * 1024;
29const TRACE_BUF: usize = 1024;
30const 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.
35pub type NetDriver<'a> = ch::Device<'a, MTU>;
36
37static WAKER: AtomicWaker = AtomicWaker::new();
38
39/// Call this function on IPC IRQ
40pub 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
49struct Allocator<'a> {
50 start: *mut u8,
51 end: *mut u8,
52 _phantom: PhantomData<&'a mut u8>,
53}
54
55impl<'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.
96pub 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.
105pub 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.
115async 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.
246pub struct TraceBuffer {
247 trace: pipe::Pipe<NoopRawMutex, TRACE_BUF>,
248}
249
250/// Represents writer half of the trace buffer.
251pub type TraceWriter<'a> = pipe::Writer<'a, NoopRawMutex, TRACE_BUF>;
252
253/// Represents the reader half of the trace buffer.
254pub type TraceReader<'a> = pipe::Reader<'a, NoopRawMutex, TRACE_BUF>;
255
256impl 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.
266pub struct State {
267 ch: ch::State<MTU, 4, 4>,
268 inner: MaybeUninit<RefCell<StateInner>>,
269}
270
271impl 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
281const TX_BUF_COUNT: usize = 4;
282const TX_BUF_SIZE: usize = 1500;
283
284struct TraceChannelInfo {
285 ptr: *mut TraceChannel,
286 start: *mut u8,
287 end: *mut u8,
288}
289
290const REQ_COUNT: usize = 4;
291
292struct 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))]
300struct NoFreeBufs;
301
302struct 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
322impl 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
691struct PointerChecker {
692 start: *mut u8,
693 end: *mut u8,
694}
695
696impl 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.
725pub struct Control<'a> {
726 state: &'a RefCell<StateInner>,
727}
728
729impl<'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(&param);
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.
891pub struct Runner<'a> {
892 ch: ch::Runner<'a, MTU>,
893 state: &'a RefCell<StateInner>,
894 trace_writer: Option<TraceWriter<'a>>,
895}
896
897impl<'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
927const LIST_LEN: usize = 16;
928
929#[repr(C)]
930struct 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)]
951struct 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)]
959struct 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
970const TRACE_CHANNEL_COUNT: usize = 3;
971
972#[repr(C)]
973#[cfg_attr(feature = "defmt", derive(defmt::Format))]
974struct 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))]
983struct 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)]
995struct List {
996 len: usize,
997 items: [ListItem; LIST_LEN],
998}
999
1000#[repr(C)]
1001struct 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)]
1013struct 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
1028struct OnDrop<F: FnOnce()> {
1029 f: MaybeUninit<F>,
1030}
1031
1032impl<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
1042impl<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"))']
2runner = "probe-rs run --chip nRF9160_xxAA" 2# runner = "probe-rs run --chip nRF9160_xxAA"
3runner = [ "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]
5target = "thumbv8m.main-none-eabihf" 6target = "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"
8embassy-executor = { version = "0.6.0", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] } 8embassy-executor = { version = "0.6.0", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] }
9embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime"] } 9embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime"] }
10embassy-nrf = { version = "0.2.0", path = "../../embassy-nrf", features = ["defmt", "nrf9160-s", "time-driver-rtc1", "gpiote", "unstable-pac", "time"] } 10embassy-nrf = { version = "0.2.0", path = "../../embassy-nrf", features = ["defmt", "nrf9160-s", "time-driver-rtc1", "gpiote", "unstable-pac", "time"] }
11embassy-net-nrf91 = { version = "0.1.0", path = "../../embassy-net-nrf91", features = ["defmt"] }
12embassy-net = { version = "0.4.0", path = "../../embassy-net", features = ["defmt", "tcp", "proto-ipv4", "medium-ip"] }
11 13
12defmt = "0.3" 14defmt = "0.3"
13defmt-rtt = "0.4" 15defmt-rtt = "0.4"
14 16
17heapless = "0.8"
15cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } 18cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] }
16cortex-m-rt = "0.7.0" 19cortex-m-rt = "0.7.0"
17panic-probe = { version = "0.3", features = ["print-defmt"] } 20panic-probe = { version = "0.3", features = ["print-defmt"] }
21static_cell = { version = "2" }
22embedded-io = "0.6.1"
23embedded-io-async = { version = "0.6.1", features = ["defmt-03"] }
18 24
19[profile.release] 25[profile.release]
20debug = 2 26debug = 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 @@
1MEMORY 1MEMORY
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
8PROVIDE(__start_ipc = ORIGIN(IPC));
9PROVIDE(__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
4use core::mem::MaybeUninit;
5use core::net::IpAddr;
6use core::ptr::addr_of_mut;
7use core::slice;
8use core::str::FromStr;
9
10use defmt::{info, unwrap, warn};
11use embassy_executor::Spawner;
12use embassy_net::{Ipv4Address, Ipv4Cidr, Stack, StackResources};
13use embassy_net_nrf91::context::Status;
14use embassy_net_nrf91::{context, Runner, State, TraceBuffer, TraceReader};
15use embassy_nrf::buffered_uarte::{self, BufferedUarteTx};
16use embassy_nrf::gpio::{AnyPin, Level, Output, OutputDrive, Pin};
17use embassy_nrf::uarte::Baudrate;
18use embassy_nrf::{bind_interrupts, interrupt, peripherals, uarte};
19use embassy_time::{Duration, Timer};
20use embedded_io_async::Write;
21use heapless::Vec;
22use static_cell::StaticCell;
23use {defmt_rtt as _, panic_probe as _};
24
25#[interrupt]
26fn IPC() {
27 embassy_net_nrf91::on_ipc_irq();
28}
29
30bind_interrupts!(struct Irqs {
31 UARTE0_SPIM0_SPIS0_TWIM0_TWIS0 => buffered_uarte::InterruptHandler<peripherals::SERIAL0>;
32});
33
34#[embassy_executor::task]
35async 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]
44async fn modem_task(runner: Runner<'static>) -> ! {
45 runner.run().await
46}
47
48#[embassy_executor::task]
49async fn net_task(stack: &'static Stack<embassy_net_nrf91::NetDriver<'static>>) -> ! {
50 stack.run().await
51}
52
53#[embassy_executor::task]
54async 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
69fn 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]
96async 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
106extern "C" {
107 static __start_ipc: u8;
108 static __end_ipc: u8;
109}
110
111#[embassy_executor::main]
112async 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}