1#![no_std]
2#![doc = include_str!("../README.md")]
3use crate::error::ErrorSource;
6use core::{
7 cell::RefCell,
8 ops::Range,
9 sync::atomic::{AtomicBool, Ordering},
10};
11use critical_section::Mutex;
12use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
13use linked_list_allocator::Heap;
14
15mod at;
16mod at_notifications;
17mod cancellation;
18mod dns;
19mod dtls_socket;
20pub(crate) mod embedded_io_macros;
21mod error;
22pub mod ffi;
23mod gnss;
24pub(crate) mod ip;
25mod lte_link;
26mod sms;
27pub(crate) mod socket;
28mod tcp_stream;
29mod tls_stream;
30mod udp_socket;
31pub(crate) mod waker_node_list;
32
33pub use nrfxlib_sys;
34
35pub use at::*;
36pub use at_notifications::AtNotificationStream;
37pub use cancellation::CancellationToken;
38pub use dns::*;
39pub use dtls_socket::*;
40pub use error::Error;
41pub use gnss::*;
42pub use lte_link::LteLink;
43pub use sms::*;
44pub use socket::CipherSuite;
45pub use socket::PeerVerification;
46pub use tcp_stream::*;
47pub use tls_stream::*;
48pub use udp_socket::*;
49
50#[cfg(feature = "nrf9160")]
51use nrf9160_pac as pac;
52
53#[cfg(feature = "nrf9120")]
54use nrf9120_pac as pac;
55
56type WrappedHeap = Mutex<RefCell<Option<Heap>>>;
67
68static LIBRARY_ALLOCATOR: WrappedHeap = Mutex::new(RefCell::new(None));
72
73static TX_ALLOCATOR: WrappedHeap = Mutex::new(RefCell::new(None));
78
79pub(crate) static MODEM_RUNTIME_STATE: RuntimeState = RuntimeState::new();
80static INITIALIZED: AtomicBool = AtomicBool::new(false);
81
82pub async fn init(mode: SystemMode, #[cfg(feature = "os-irq")] os_irq: u8) -> Result<(), Error> {
87 init_with_custom_layout(
88 mode,
89 Default::default(),
90 #[cfg(feature = "os-irq")]
91 os_irq,
92 )
93 .await
94}
95
96pub async fn init_with_custom_layout(
101 mode: SystemMode,
102 memory_layout: MemoryLayout,
103 #[cfg(feature = "os-irq")] os_irq: u8,
104) -> Result<(), Error> {
105 if INITIALIZED.fetch_or(true, Ordering::SeqCst) {
106 return Err(Error::ModemAlreadyInitialized);
107 }
108
109 #[cfg(feature = "os-irq")]
110 ffi::OS_IRQ.store(os_irq, Ordering::Relaxed);
111
112 const SHARED_MEMORY_RANGE: Range<u32> = 0x2000_0000..0x2002_0000;
113
114 if !SHARED_MEMORY_RANGE.contains(&memory_layout.base_address) {
115 return Err(Error::BadMemoryLayout);
116 }
117
118 if !SHARED_MEMORY_RANGE.contains(
119 &(memory_layout.base_address
120 + nrfxlib_sys::NRF_MODEM_CELLULAR_SHMEM_CTRL_SIZE
121 + memory_layout.tx_area_size
122 + memory_layout.rx_area_size
123 + memory_layout.trace_area_size
124 - 1),
126 ) {
127 return Err(Error::BadMemoryLayout);
128 }
129
130 #[cfg(feature = "modem-trace")]
131 if memory_layout.trace_area_size == 0 {
132 return Err(Error::BadMemoryLayout);
133 }
134
135 unsafe {
137 (*pac::REGULATORS_NS::PTR)
138 .dcdcen
139 .modify(|_, w| w.dcdcen().enabled());
140 }
141
142 unsafe {
143 const HEAP_SIZE: usize = 1024;
144 static mut HEAP_MEMORY: [u32; HEAP_SIZE] = [0u32; HEAP_SIZE];
146 let heap_start = &raw mut HEAP_MEMORY;
147 let heap_size = HEAP_SIZE * core::mem::size_of::<u32>();
148 critical_section::with(|cs| {
149 *LIBRARY_ALLOCATOR.borrow(cs).borrow_mut() =
150 Some(Heap::new(heap_start.cast::<u8>(), heap_size))
151 });
152 }
153
154 static PARAMS: grounded::uninit::GroundedCell<nrfxlib_sys::nrf_modem_init_params> =
156 grounded::uninit::GroundedCell::uninit();
157
158 let params = nrfxlib_sys::nrf_modem_init_params {
159 shmem: nrfxlib_sys::nrf_modem_shmem_cfg {
160 ctrl: nrfxlib_sys::nrf_modem_shmem_cfg__bindgen_ty_1 {
161 base: memory_layout.base_address,
162 size: nrfxlib_sys::NRF_MODEM_CELLULAR_SHMEM_CTRL_SIZE,
163 },
164 tx: nrfxlib_sys::nrf_modem_shmem_cfg__bindgen_ty_2 {
165 base: memory_layout.base_address + nrfxlib_sys::NRF_MODEM_CELLULAR_SHMEM_CTRL_SIZE,
166 size: memory_layout.tx_area_size,
167 },
168 rx: nrfxlib_sys::nrf_modem_shmem_cfg__bindgen_ty_3 {
169 base: memory_layout.base_address
170 + nrfxlib_sys::NRF_MODEM_CELLULAR_SHMEM_CTRL_SIZE
171 + memory_layout.tx_area_size,
172 size: memory_layout.rx_area_size,
173 },
174 trace: nrfxlib_sys::nrf_modem_shmem_cfg__bindgen_ty_4 {
175 base: memory_layout.base_address
176 + nrfxlib_sys::NRF_MODEM_CELLULAR_SHMEM_CTRL_SIZE
177 + memory_layout.tx_area_size
178 + memory_layout.rx_area_size,
179 size: memory_layout.trace_area_size,
180 },
181 },
182 ipc_irq_prio: 0,
183 fault_handler: Some(modem_fault_handler),
184 dfu_handler: Some(modem_dfu_handler),
185 };
186
187 critical_section::with(|_| unsafe { PARAMS.get().write(params) });
188
189 unsafe {
190 critical_section::with(|cs| {
192 *TX_ALLOCATOR.borrow(cs).borrow_mut() = Some(Heap::new(
193 params.shmem.tx.base as usize as *mut u8,
194 params.shmem.tx.size as usize,
195 ))
196 });
197 }
198
199 unsafe { nrfxlib_sys::nrf_modem_init(PARAMS.get()) }.into_result()?;
201
202 #[cfg(feature = "modem-trace")]
204 at::send_at::<0>("AT%XMODEMTRACE=1,2").await?;
205
206 at_notifications::initialize()?;
208
209 let (modem_state,) =
211 at_commands::parser::CommandParser::parse(at::send_at::<32>("AT+CFUN?").await?.as_bytes())
212 .expect_identifier(b"+CFUN: ")
213 .expect_int_parameter()
214 .expect_identifier(b"\r\nOK\r\n")
215 .finish()?;
216
217 if modem_state != 0 {
218 at::send_at::<0>("AT+CFUN=0").await?;
220 }
221
222 if !mode.is_valid_config() {
223 return Err(Error::InvalidSystemModeConfig);
224 }
225
226 let mut buffer = [0; 64];
227 let command = mode.create_at_command(&mut buffer)?;
228 at::send_at_bytes::<0>(command).await?;
229
230 mode.setup_psm().await?;
231
232 Ok(())
233}
234
235#[cfg(feature = "modem-trace")]
242pub async fn fetch_traces(cb: impl AsyncFn(&[u8])) -> Result<(), Error> {
243 let mut frags: *mut nrfxlib_sys::nrf_modem_trace_data = core::ptr::null_mut();
244 let mut nfrags = 0;
245
246 let res = unsafe {
247 nrfxlib_sys::nrf_modem_trace_get(
248 &mut frags,
249 &mut nfrags,
250 nrfxlib_sys::NRF_MODEM_OS_NO_WAIT as i32,
251 )
252 };
253
254 if res != 0 {
255 return Err(Error::NrfError(res as isize));
256 }
257
258 let frags = unsafe { core::slice::from_raw_parts(frags, nfrags) };
260 for nrfxlib_sys::nrf_modem_trace_data { data, len } in frags {
261 let data = unsafe { core::slice::from_raw_parts(*data as *mut u8, *len) };
262 cb(data).await;
263 unsafe {
264 nrfxlib_sys::nrf_modem_trace_processed(*len);
265 }
266 }
267
268 Ok(())
269}
270
271pub struct MemoryLayout {
278 pub base_address: u32,
280 pub tx_area_size: u32,
282 pub rx_area_size: u32,
284 pub trace_area_size: u32,
286}
287
288impl Default for MemoryLayout {
289 fn default() -> Self {
290 Self {
291 base_address: 0x2001_0000,
292 tx_area_size: 0x2000,
293 rx_area_size: 0x2000,
294 trace_area_size: if cfg!(feature = "modem-trace") {
295 0x2000
296 } else {
297 0
298 },
299 }
300 }
301}
302
303unsafe extern "C" fn modem_fault_handler(_info: *mut nrfxlib_sys::nrf_modem_fault_info) {
304 #[cfg(feature = "defmt")]
305 defmt::error!(
306 "Modem fault - reason: {}, pc: {}",
307 (*_info).reason,
308 (*_info).program_counter
309 );
310}
311
312unsafe extern "C" fn modem_dfu_handler(_val: u32) {
313 #[cfg(feature = "defmt")]
314 defmt::trace!("Modem DFU handler");
315}
316
317pub fn ipc_irq_handler() {
319 unsafe {
320 crate::ffi::nrf_ipc_irq_handler();
321 }
322 cortex_m::asm::sev();
323}
324
325#[derive(Debug, Copy, Clone)]
329pub struct SystemMode {
330 pub lte_support: bool,
332 pub lte_psm_support: bool,
334 pub nbiot_support: bool,
336 pub gnss_support: bool,
338 pub preference: ConnectionPreference,
340}
341
342#[derive(Debug, Copy, Clone)]
344pub enum ConnectionPreference {
345 None = 0,
347 Lte = 1,
349 Nbiot = 2,
351 NetworkPreferenceWithLteFallback = 3,
353 NetworkPreferenceWithNbiotFallback = 4,
355}
356
357impl SystemMode {
358 fn is_valid_config(&self) -> bool {
359 if self.lte_psm_support && !self.lte_support {
360 return false;
361 }
362 match self.preference {
363 ConnectionPreference::None => true,
364 ConnectionPreference::Lte => self.lte_support,
365 ConnectionPreference::Nbiot => self.nbiot_support,
366 ConnectionPreference::NetworkPreferenceWithLteFallback => {
367 self.lte_support && self.nbiot_support
368 }
369 ConnectionPreference::NetworkPreferenceWithNbiotFallback => {
370 self.lte_support && self.nbiot_support
371 }
372 }
373 }
374
375 fn create_at_command<'a>(&self, buffer: &'a mut [u8]) -> Result<&'a [u8], Error> {
376 at_commands::builder::CommandBuilder::create_set(buffer, true)
377 .named("%XSYSTEMMODE")
378 .with_int_parameter(self.lte_support as u8)
379 .with_int_parameter(self.nbiot_support as u8)
380 .with_int_parameter(self.gnss_support as u8)
381 .with_int_parameter(self.preference as u8)
382 .finish()
383 .map_err(|e| Error::BufferTooSmall(Some(e)))
384 }
385
386 async fn setup_psm(&self) -> Result<(), Error> {
387 if self.lte_support {
388 if self.lte_psm_support {
389 at::send_at::<0>("AT+CPSMS=1").await?;
391 } else {
392 at::send_at::<0>("AT+CPSMS=0").await?;
394 }
395 }
396 Ok(())
397 }
398}
399
400#[cfg(feature = "nrf9160")]
408pub async fn configure_gnss_on_pca10090ns() -> Result<(), Error> {
409 #[cfg(feature = "defmt")]
410 defmt::debug!("Configuring XMAGPIO pins for 1574-1577 MHz");
411
412 crate::at::send_at::<0>("AT%XMAGPIO=1,0,0,1,1,1574,1577").await?;
414 Ok(())
415}
416
417struct RuntimeState {
418 state: embassy_sync::mutex::Mutex<CriticalSectionRawMutex, (bool, u16)>,
419 error: AtomicBool,
420}
421
422impl RuntimeState {
423 const fn new() -> Self {
424 Self {
425 state: embassy_sync::mutex::Mutex::new((false, 0)),
426 error: AtomicBool::new(false),
427 }
428 }
429
430 pub(crate) async fn activate_gps(&self) -> Result<(), Error> {
431 let mut state = self.state.lock().await;
432
433 if state.0 {
434 return Err(Error::GnssAlreadyTaken);
435 }
436
437 ModemActivation::Gnss.act_on_modem().await?;
438
439 state.0 = true;
440
441 Ok(())
442 }
443
444 pub(crate) async fn deactivate_gps(&self) -> Result<(), Error> {
445 let mut state = self.state.lock().await;
446
447 if !state.0 {
448 panic!("Can't deactivate an inactive gps");
449 }
450
451 if state.1 == 0 {
452 ModemDeactivation::Everything.act_on_modem().await?;
453 } else {
454 ModemDeactivation::OnlyGnss.act_on_modem().await?;
455 }
456
457 state.0 = false;
458
459 Ok(())
460 }
461
462 pub(crate) fn deactivate_gps_blocking(&self) -> Result<(), Error> {
463 let mut state = self
464 .state
465 .try_lock()
466 .map_err(|_| Error::InternalRuntimeMutexLocked)?;
467
468 if !state.0 {
469 panic!("Can't deactivate an inactive gps");
470 }
471
472 if state.1 == 0 {
473 ModemDeactivation::Everything.act_on_modem_blocking()?;
474 } else {
475 ModemDeactivation::OnlyGnss.act_on_modem_blocking()?;
476 }
477
478 state.0 = false;
479
480 Ok(())
481 }
482
483 pub(crate) async fn activate_lte(&self) -> Result<(), Error> {
484 let mut state = self.state.lock().await;
485
486 if state.1 == u16::MAX {
487 return Err(Error::TooManyLteLinks);
488 }
489
490 if state.1 == 0 {
491 ModemActivation::Lte.act_on_modem().await?;
492 }
493
494 state.1 += 1;
495
496 Ok(())
497 }
498
499 pub(crate) async fn deactivate_lte(&self) -> Result<(), Error> {
500 let mut state = self.state.lock().await;
501
502 if state.1 == 0 {
503 panic!("Can't deactivate an inactive lte");
504 }
505
506 if state.1 == 1 {
507 if state.0 {
508 ModemDeactivation::OnlyLte
509 } else {
510 ModemDeactivation::Everything
511 }
512 } else {
513 ModemDeactivation::Nothing
514 }
515 .act_on_modem()
516 .await?;
517
518 state.1 -= 1;
519
520 Ok(())
521 }
522
523 pub(crate) fn deactivate_lte_blocking(&self) -> Result<(), Error> {
524 let mut state = self
525 .state
526 .try_lock()
527 .map_err(|_| Error::InternalRuntimeMutexLocked)?;
528
529 if state.1 == 0 {
530 panic!("Can't deactivate an inactive lte");
531 }
532
533 if state.1 == 1 {
534 if state.0 {
535 ModemDeactivation::OnlyLte
536 } else {
537 ModemDeactivation::Everything
538 }
539 } else {
540 ModemDeactivation::Nothing
541 }
542 .act_on_modem_blocking()?;
543
544 state.1 -= 1;
545
546 Ok(())
547 }
548
549 pub(crate) fn set_error_active(&self) {
550 self.error.store(true, Ordering::SeqCst);
551 }
552
553 pub(crate) fn get_error_active(&self) -> bool {
554 self.error.load(Ordering::SeqCst)
555 }
556
557 pub(crate) async unsafe fn reset_runtime_state(&self) -> Result<(), Error> {
558 let mut state = self.state.lock().await;
559
560 if self
561 .error
562 .compare_exchange(true, false, Ordering::SeqCst, Ordering::SeqCst)
563 .is_ok()
564 {
565 ModemDeactivation::Everything.act_on_modem().await?;
566 state.0 = false;
567 state.1 = 0;
568 }
569
570 self.error.store(false, Ordering::SeqCst);
571
572 Ok(())
573 }
574}
575
576pub fn has_runtime_state_error() -> bool {
581 MODEM_RUNTIME_STATE.get_error_active()
582}
583
584pub async unsafe fn reset_runtime_state() -> Result<(), Error> {
590 MODEM_RUNTIME_STATE.reset_runtime_state().await
591}
592
593enum ModemDeactivation {
594 OnlyGnss,
595 OnlyLte,
596 Nothing,
597 Everything,
598}
599
600impl ModemDeactivation {
601 async fn act_on_modem(&self) -> Result<(), Error> {
602 match self {
603 ModemDeactivation::OnlyGnss => {
604 #[cfg(feature = "defmt")]
605 defmt::debug!("Disabling modem GNSS");
606
607 at::send_at::<0>("AT+CFUN=30").await?;
608 }
609 ModemDeactivation::OnlyLte => {
610 #[cfg(feature = "defmt")]
611 defmt::debug!("Disabling modem LTE");
612
613 at::send_at::<0>("AT+CFUN=20").await?;
615 at::send_at::<0>("AT+CFUN=40").await?;
617 }
618 ModemDeactivation::Nothing => {}
619 ModemDeactivation::Everything => {
620 #[cfg(feature = "defmt")]
621 defmt::debug!("Disabling full modem");
622
623 at::send_at::<0>("AT+CFUN=0").await?;
624 }
625 }
626
627 Ok(())
628 }
629
630 fn act_on_modem_blocking(&self) -> Result<(), Error> {
631 match self {
632 ModemDeactivation::OnlyGnss => {
633 #[cfg(feature = "defmt")]
634 defmt::debug!("Disabling modem GNSS");
635
636 at::send_at_blocking::<0>("AT+CFUN=30")?;
637 }
638 ModemDeactivation::OnlyLte => {
639 #[cfg(feature = "defmt")]
640 defmt::debug!("Disabling modem LTE");
641
642 at::send_at_blocking::<0>("AT+CFUN=20")?;
644 at::send_at_blocking::<0>("AT+CFUN=40")?;
646 }
647 ModemDeactivation::Nothing => {}
648 ModemDeactivation::Everything => {
649 #[cfg(feature = "defmt")]
650 defmt::debug!("Disabling full modem");
651
652 at::send_at_blocking::<0>("AT+CFUN=0")?;
653 }
654 }
655
656 Ok(())
657 }
658}
659
660enum ModemActivation {
661 Lte,
662 Gnss,
663}
664
665impl ModemActivation {
666 async fn act_on_modem(&self) -> Result<(), Error> {
667 match self {
668 ModemActivation::Gnss => {
669 #[cfg(feature = "defmt")]
670 defmt::debug!("Enabling modem GNSS");
671
672 at::send_at::<0>("AT+CFUN=31").await?;
673 }
674 ModemActivation::Lte => {
675 #[cfg(feature = "defmt")]
676 defmt::debug!("Enabling modem LTE");
677
678 at::send_at::<0>("AT%XDATAPRFL=0").await?;
680 at::send_at::<0>("AT+CEPPI=1").await?;
682 at::send_at::<0>("AT+CFUN=21").await?;
684 }
685 }
686
687 Ok(())
688 }
689}