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;
20#[cfg(feature = "embassy-net")]
21pub mod embassy_net_modem;
22pub(crate) mod embedded_io_macros;
23#[cfg(feature = "embedded-nal-async")]
24mod embedded_nal_async;
25mod error;
26pub mod ffi;
27mod gnss;
28pub(crate) mod ip;
29mod lte_link;
30mod sms;
31pub mod socket;
33mod tcp_stream;
34mod tls_stream;
35mod udp_socket;
36mod uicc_link;
37pub(crate) mod waker_node_list;
38
39pub use nrfxlib_sys;
40
41pub use at::*;
42pub use at_notifications::AtNotificationStream;
43pub use cancellation::CancellationToken;
44pub use dns::*;
45pub use dtls_socket::*;
46#[cfg(feature = "embedded-nal-async")]
47pub use embedded_nal_async::*;
48pub use error::Error;
49pub use gnss::*;
50pub use lte_link::LteLink;
51pub use sms::*;
52pub use socket::CipherSuite;
53pub use socket::PeerVerification;
54pub use tcp_stream::*;
55pub use tls_stream::*;
56pub use udp_socket::*;
57pub use uicc_link::UiccLink;
58
59#[cfg(feature = "nrf9160")]
60use nrf9160_pac as pac;
61
62#[cfg(feature = "nrf9120")]
63use nrf9120_pac as pac;
64
65type WrappedHeap = Mutex<RefCell<Option<Heap>>>;
76
77static LIBRARY_ALLOCATOR: WrappedHeap = Mutex::new(RefCell::new(None));
81
82static TX_ALLOCATOR: WrappedHeap = Mutex::new(RefCell::new(None));
87
88pub(crate) static MODEM_RUNTIME_STATE: RuntimeState = RuntimeState::new();
89static INITIALIZED: AtomicBool = AtomicBool::new(false);
90
91pub async fn init(mode: SystemMode, #[cfg(feature = "os-irq")] os_irq: u8) -> Result<(), Error> {
96 init_with_custom_layout(
97 mode,
98 Default::default(),
99 #[cfg(feature = "os-irq")]
100 os_irq,
101 )
102 .await
103}
104
105pub async fn init_with_custom_layout(
110 mode: SystemMode,
111 memory_layout: MemoryLayout,
112 #[cfg(feature = "os-irq")] os_irq: u8,
113) -> Result<(), Error> {
114 if INITIALIZED.fetch_or(true, Ordering::SeqCst) {
115 return Err(Error::ModemAlreadyInitialized);
116 }
117
118 #[cfg(feature = "os-irq")]
119 ffi::OS_IRQ.store(os_irq, Ordering::Relaxed);
120
121 const SHARED_MEMORY_RANGE: Range<u32> = 0x2000_0000..0x2002_0000;
122
123 if !SHARED_MEMORY_RANGE.contains(&memory_layout.base_address) {
124 return Err(Error::BadMemoryLayout);
125 }
126
127 if !SHARED_MEMORY_RANGE.contains(
128 &(memory_layout.base_address
129 + nrfxlib_sys::NRF_MODEM_CELLULAR_SHMEM_CTRL_SIZE
130 + memory_layout.tx_area_size
131 + memory_layout.rx_area_size
132 + memory_layout.trace_area_size
133 - 1),
135 ) {
136 return Err(Error::BadMemoryLayout);
137 }
138
139 #[cfg(feature = "modem-trace")]
140 if memory_layout.trace_area_size == 0 {
141 return Err(Error::BadMemoryLayout);
142 }
143
144 unsafe {
146 (*pac::REGULATORS_NS::PTR)
147 .dcdcen
148 .modify(|_, w| w.dcdcen().enabled());
149 }
150
151 unsafe {
152 const HEAP_SIZE: usize = 1024;
153 static mut HEAP_MEMORY: [u32; HEAP_SIZE] = [0u32; HEAP_SIZE];
155 let heap_start = &raw mut HEAP_MEMORY;
156 let heap_size = HEAP_SIZE * core::mem::size_of::<u32>();
157 critical_section::with(|cs| {
158 *LIBRARY_ALLOCATOR.borrow(cs).borrow_mut() =
159 Some(Heap::new(heap_start.cast::<u8>(), heap_size))
160 });
161 }
162
163 static PARAMS: grounded::uninit::GroundedCell<nrfxlib_sys::nrf_modem_init_params> =
165 grounded::uninit::GroundedCell::uninit();
166
167 let params = nrfxlib_sys::nrf_modem_init_params {
168 shmem: nrfxlib_sys::nrf_modem_shmem_cfg {
169 ctrl: nrfxlib_sys::nrf_modem_shmem_cfg__bindgen_ty_1 {
170 base: memory_layout.base_address,
171 size: nrfxlib_sys::NRF_MODEM_CELLULAR_SHMEM_CTRL_SIZE,
172 },
173 tx: nrfxlib_sys::nrf_modem_shmem_cfg__bindgen_ty_2 {
174 base: memory_layout.base_address + nrfxlib_sys::NRF_MODEM_CELLULAR_SHMEM_CTRL_SIZE,
175 size: memory_layout.tx_area_size,
176 },
177 rx: nrfxlib_sys::nrf_modem_shmem_cfg__bindgen_ty_3 {
178 base: memory_layout.base_address
179 + nrfxlib_sys::NRF_MODEM_CELLULAR_SHMEM_CTRL_SIZE
180 + memory_layout.tx_area_size,
181 size: memory_layout.rx_area_size,
182 },
183 trace: nrfxlib_sys::nrf_modem_shmem_cfg__bindgen_ty_4 {
184 base: memory_layout.base_address
185 + nrfxlib_sys::NRF_MODEM_CELLULAR_SHMEM_CTRL_SIZE
186 + memory_layout.tx_area_size
187 + memory_layout.rx_area_size,
188 size: memory_layout.trace_area_size,
189 },
190 },
191 ipc_irq_prio: 0,
192 fault_handler: Some(modem_fault_handler),
193 dfu_handler: Some(modem_dfu_handler),
194 };
195
196 critical_section::with(|_| unsafe { PARAMS.get().write(params) });
197
198 unsafe {
199 critical_section::with(|cs| {
201 *TX_ALLOCATOR.borrow(cs).borrow_mut() = Some(Heap::new(
202 params.shmem.tx.base as usize as *mut u8,
203 params.shmem.tx.size as usize,
204 ))
205 });
206 }
207
208 unsafe { nrfxlib_sys::nrf_modem_init(PARAMS.get()) }.into_result()?;
210
211 #[cfg(feature = "modem-trace")]
213 at::send_at::<0>("AT%XMODEMTRACE=1,2").await?;
214
215 at_notifications::initialize()?;
217
218 let (modem_state,) =
220 at_commands::parser::CommandParser::parse(at::send_at::<32>("AT+CFUN?").await?.as_bytes())
221 .expect_identifier(b"+CFUN: ")
222 .expect_int_parameter()
223 .expect_identifier(b"\r\nOK\r\n")
224 .finish()?;
225
226 if modem_state != 0 {
227 at::send_at::<0>("AT+CFUN=0").await?;
229 }
230
231 if !mode.is_valid_config() {
232 return Err(Error::InvalidSystemModeConfig);
233 }
234
235 let mut buffer = [0; 64];
236 let command = mode.create_at_command(&mut buffer)?;
237 at::send_at_bytes::<0>(command).await?;
238
239 mode.setup_psm().await?;
240
241 Ok(())
242}
243
244#[cfg(feature = "modem-trace")]
251pub async fn fetch_traces(cb: impl AsyncFn(&[u8])) -> Result<(), Error> {
252 let mut frags: *mut nrfxlib_sys::nrf_modem_trace_data = core::ptr::null_mut();
253 let mut nfrags = 0;
254
255 let res = unsafe {
256 nrfxlib_sys::nrf_modem_trace_get(
257 &mut frags,
258 &mut nfrags,
259 nrfxlib_sys::NRF_MODEM_OS_NO_WAIT as i32,
260 )
261 };
262
263 if res != 0 {
264 return Err(Error::NrfError(res as isize));
265 }
266
267 let frags = unsafe { core::slice::from_raw_parts(frags, nfrags) };
269 for nrfxlib_sys::nrf_modem_trace_data { data, len } in frags {
270 let data = unsafe { core::slice::from_raw_parts(*data as *mut u8, *len) };
271 cb(data).await;
272 unsafe {
273 nrfxlib_sys::nrf_modem_trace_processed(*len);
274 }
275 }
276
277 Ok(())
278}
279
280pub struct MemoryLayout {
287 pub base_address: u32,
289 pub tx_area_size: u32,
291 pub rx_area_size: u32,
293 pub trace_area_size: u32,
295}
296
297impl Default for MemoryLayout {
298 fn default() -> Self {
299 Self {
300 base_address: 0x2001_0000,
301 tx_area_size: 0x2000,
302 rx_area_size: 0x2000,
303 trace_area_size: if cfg!(feature = "modem-trace") {
304 0x2000
305 } else {
306 0
307 },
308 }
309 }
310}
311
312unsafe extern "C" fn modem_fault_handler(info: *mut nrfxlib_sys::nrf_modem_fault_info) {
313 #[cfg(feature = "defmt")]
314 defmt::panic!(
315 "Modem fault - reason: {}, pc: {}",
316 (*info).reason,
317 (*info).program_counter
318 );
319 #[cfg(not(feature = "defmt"))]
320 panic!(
321 "Modem fault - reason: {}, pc: {}",
322 (*info).reason,
323 (*info).program_counter
324 );
325}
326
327unsafe extern "C" fn modem_dfu_handler(_val: u32) {
328 #[cfg(feature = "defmt")]
329 defmt::trace!("Modem DFU handler");
330}
331
332pub fn ipc_irq_handler() {
334 unsafe {
335 crate::ffi::nrf_ipc_irq_handler();
336 }
337 cortex_m::asm::sev();
338}
339
340#[derive(Debug, Copy, Clone)]
344pub struct SystemMode {
345 pub lte_support: bool,
347 pub lte_psm_support: bool,
349 pub nbiot_support: bool,
351 pub gnss_support: bool,
353 pub preference: ConnectionPreference,
355}
356
357#[derive(Debug, Copy, Clone)]
359pub enum ConnectionPreference {
360 None = 0,
362 Lte = 1,
364 Nbiot = 2,
366 NetworkPreferenceWithLteFallback = 3,
368 NetworkPreferenceWithNbiotFallback = 4,
370}
371
372impl SystemMode {
373 fn is_valid_config(&self) -> bool {
374 if self.lte_psm_support && !self.lte_support {
375 return false;
376 }
377 match self.preference {
378 ConnectionPreference::None => true,
379 ConnectionPreference::Lte => self.lte_support,
380 ConnectionPreference::Nbiot => self.nbiot_support,
381 ConnectionPreference::NetworkPreferenceWithLteFallback => {
382 self.lte_support && self.nbiot_support
383 }
384 ConnectionPreference::NetworkPreferenceWithNbiotFallback => {
385 self.lte_support && self.nbiot_support
386 }
387 }
388 }
389
390 fn create_at_command<'a>(&self, buffer: &'a mut [u8]) -> Result<&'a [u8], Error> {
391 at_commands::builder::CommandBuilder::create_set(buffer, true)
392 .named("%XSYSTEMMODE")
393 .with_int_parameter(self.lte_support as u8)
394 .with_int_parameter(self.nbiot_support as u8)
395 .with_int_parameter(self.gnss_support as u8)
396 .with_int_parameter(self.preference as u8)
397 .finish()
398 .map_err(|e| Error::BufferTooSmall(Some(e)))
399 }
400
401 async fn setup_psm(&self) -> Result<(), Error> {
402 if self.lte_support {
403 if self.lte_psm_support {
404 at::send_at::<0>("AT+CPSMS=1").await?;
406 } else {
407 at::send_at::<0>("AT+CPSMS=0").await?;
409 }
410 }
411 Ok(())
412 }
413}
414
415#[cfg(feature = "nrf9160")]
423pub async fn configure_gnss_on_pca10090ns() -> Result<(), Error> {
424 #[cfg(feature = "defmt")]
425 defmt::debug!("Configuring XMAGPIO pins for 1574-1577 MHz");
426
427 crate::at::send_at::<0>("AT%XMAGPIO=1,0,0,1,1,1574,1577").await?;
429 Ok(())
430}
431
432struct RuntimeState {
433 state: embassy_sync::mutex::Mutex<CriticalSectionRawMutex, RuntimeStateInner>,
434 error: AtomicBool,
435}
436
437struct RuntimeStateInner {
438 gps_active: bool,
439 lte_link_count: u16,
440 uicc_link_count: u16,
441}
442
443impl RuntimeState {
444 const fn new() -> Self {
445 Self {
446 state: embassy_sync::mutex::Mutex::new(RuntimeStateInner {
447 gps_active: false,
448 lte_link_count: 0,
449 uicc_link_count: 0,
450 }),
451 error: AtomicBool::new(false),
452 }
453 }
454
455 pub(crate) async fn activate_gps(&self) -> Result<(), Error> {
456 let mut state = self.state.lock().await;
457
458 if state.gps_active {
459 return Err(Error::GnssAlreadyTaken);
460 }
461
462 ModemActivation::Gnss.act_on_modem().await?;
463
464 state.gps_active = true;
465
466 Ok(())
467 }
468
469 pub(crate) async fn deactivate_gps(&self) -> Result<(), Error> {
470 let mut state = self.state.lock().await;
471
472 if !state.gps_active {
473 panic!("Can't deactivate an inactive gps");
474 }
475
476 if state.lte_link_count == 0 && state.uicc_link_count == 0 {
477 ModemDeactivation::Everything.act_on_modem().await?;
478 } else {
479 ModemDeactivation::OnlyGnss.act_on_modem().await?;
480 }
481
482 state.gps_active = false;
483
484 Ok(())
485 }
486
487 pub(crate) fn deactivate_gps_blocking(&self) -> Result<(), Error> {
488 let mut state = self
489 .state
490 .try_lock()
491 .map_err(|_| Error::InternalRuntimeMutexLocked)?;
492
493 if !state.gps_active {
494 panic!("Can't deactivate an inactive gps");
495 }
496
497 if state.lte_link_count == 0 && state.uicc_link_count == 0 {
498 ModemDeactivation::Everything.act_on_modem_blocking()?;
499 } else {
500 ModemDeactivation::OnlyGnss.act_on_modem_blocking()?;
501 }
502
503 state.gps_active = false;
504
505 Ok(())
506 }
507
508 pub(crate) async fn activate_lte(&self) -> Result<(), Error> {
509 let mut state = self.state.lock().await;
510
511 if state.lte_link_count == u16::MAX {
512 return Err(Error::TooManyLteLinks);
513 }
514
515 if state.lte_link_count == 0 {
516 ModemActivation::Lte.act_on_modem().await?;
517 }
518
519 state.lte_link_count += 1;
520
521 Ok(())
522 }
523
524 pub(crate) async fn deactivate_lte(&self) -> Result<(), Error> {
525 let mut state = self.state.lock().await;
526
527 if state.lte_link_count == 0 {
528 panic!("Can't deactivate an inactive lte");
529 }
530
531 if state.lte_link_count == 1 {
532 if !state.gps_active && state.uicc_link_count == 0 {
533 ModemDeactivation::Everything.act_on_modem().await?;
534 } else {
535 ModemDeactivation::OnlyLte.act_on_modem().await?;
536 if state.uicc_link_count == 0 {
537 ModemDeactivation::OnlyUicc.act_on_modem().await?;
538 }
539 }
540 }
541
542 state.lte_link_count -= 1;
543
544 Ok(())
545 }
546
547 pub(crate) fn deactivate_lte_blocking(&self) -> Result<(), Error> {
548 let mut state = self
549 .state
550 .try_lock()
551 .map_err(|_| Error::InternalRuntimeMutexLocked)?;
552
553 if state.lte_link_count == 0 {
554 panic!("Can't deactivate an inactive lte");
555 }
556
557 if state.lte_link_count == 1 {
558 if !state.gps_active && state.uicc_link_count == 0 {
559 ModemDeactivation::Everything.act_on_modem_blocking()?;
560 } else {
561 ModemDeactivation::OnlyLte.act_on_modem_blocking()?;
562 if state.uicc_link_count == 0 {
563 ModemDeactivation::OnlyUicc.act_on_modem_blocking()?;
564 }
565 }
566 }
567
568 state.lte_link_count -= 1;
569
570 Ok(())
571 }
572
573 pub(crate) async fn activate_uicc(&self) -> Result<(), Error> {
574 let mut state = self.state.lock().await;
575
576 if state.uicc_link_count == u16::MAX {
577 return Err(Error::TooManyUiccLinks);
578 }
579
580 if state.uicc_link_count == 0 {
581 ModemActivation::Uicc.act_on_modem().await?;
582 }
583
584 state.uicc_link_count += 1;
585
586 Ok(())
587 }
588
589 pub(crate) async fn deactivate_uicc(&self) -> Result<(), Error> {
590 let mut state = self.state.lock().await;
591
592 if state.uicc_link_count == 0 {
593 panic!("Can't deactivate an inactive UICC");
594 }
595
596 if state.uicc_link_count == 1 {
597 if state.gps_active || state.lte_link_count > 0 {
598 ModemDeactivation::OnlyUicc
599 } else {
600 ModemDeactivation::Everything
601 }
602 } else {
603 ModemDeactivation::Nothing
604 }
605 .act_on_modem()
606 .await?;
607
608 state.uicc_link_count -= 1;
609
610 Ok(())
611 }
612
613 pub(crate) fn deactivate_uicc_blocking(&self) -> Result<(), Error> {
614 let mut state = self
615 .state
616 .try_lock()
617 .map_err(|_| Error::InternalRuntimeMutexLocked)?;
618
619 if state.uicc_link_count == 0 {
620 panic!("Can't deactivate an inactive UICC");
621 }
622
623 if state.uicc_link_count == 1 {
624 if state.gps_active || state.lte_link_count > 0 {
625 ModemDeactivation::OnlyUicc
626 } else {
627 ModemDeactivation::Everything
628 }
629 } else {
630 ModemDeactivation::Nothing
631 }
632 .act_on_modem_blocking()?;
633
634 state.uicc_link_count -= 1;
635
636 Ok(())
637 }
638
639 pub(crate) fn set_error_active(&self) {
640 self.error.store(true, Ordering::SeqCst);
641 }
642
643 pub(crate) fn get_error_active(&self) -> bool {
644 self.error.load(Ordering::SeqCst)
645 }
646
647 pub(crate) async unsafe fn reset_runtime_state(&self) -> Result<(), Error> {
648 let mut state = self.state.lock().await;
649
650 if self
651 .error
652 .compare_exchange(true, false, Ordering::SeqCst, Ordering::SeqCst)
653 .is_ok()
654 {
655 ModemDeactivation::Everything.act_on_modem().await?;
656 state.gps_active = false;
657 state.lte_link_count = 0;
658 state.uicc_link_count = 0;
659 }
660
661 self.error.store(false, Ordering::SeqCst);
662
663 Ok(())
664 }
665}
666
667pub fn has_runtime_state_error() -> bool {
672 MODEM_RUNTIME_STATE.get_error_active()
673}
674
675pub async unsafe fn reset_runtime_state() -> Result<(), Error> {
681 MODEM_RUNTIME_STATE.reset_runtime_state().await
682}
683
684enum ModemDeactivation {
685 OnlyGnss,
686 OnlyLte,
687 OnlyUicc,
688 Nothing,
689 Everything,
690}
691
692impl ModemDeactivation {
693 async fn act_on_modem(&self) -> Result<(), Error> {
694 match self {
695 ModemDeactivation::OnlyGnss => {
696 #[cfg(feature = "defmt")]
697 defmt::debug!("Disabling modem GNSS");
698
699 at::send_at::<0>("AT+CFUN=30").await?;
700 }
701 ModemDeactivation::OnlyLte => {
702 #[cfg(feature = "defmt")]
703 defmt::debug!("Disabling modem LTE");
704
705 at::send_at::<0>("AT+CFUN=20").await?;
707 }
709 ModemDeactivation::OnlyUicc => {
710 #[cfg(feature = "defmt")]
711 defmt::debug!("Disabling UICC");
712 at::send_at::<0>("AT+CFUN=40").await?;
714 }
715 ModemDeactivation::Nothing => {}
716 ModemDeactivation::Everything => {
717 #[cfg(feature = "defmt")]
718 defmt::debug!("Disabling full modem");
719
720 at::send_at::<0>("AT+CFUN=0").await?;
721 }
722 }
723
724 Ok(())
725 }
726
727 fn act_on_modem_blocking(&self) -> Result<(), Error> {
728 match self {
729 ModemDeactivation::OnlyGnss => {
730 #[cfg(feature = "defmt")]
731 defmt::debug!("Disabling modem GNSS");
732
733 at::send_at_blocking::<0>("AT+CFUN=30")?;
734 }
735 ModemDeactivation::OnlyLte => {
736 #[cfg(feature = "defmt")]
737 defmt::debug!("Disabling modem LTE");
738
739 at::send_at_blocking::<0>("AT+CFUN=20")?;
741 }
743 ModemDeactivation::OnlyUicc => {
744 #[cfg(feature = "defmt")]
745 defmt::debug!("Disabling UICC");
746 at::send_at_blocking::<0>("AT+CFUN=40")?;
748 }
749 ModemDeactivation::Nothing => {}
750 ModemDeactivation::Everything => {
751 #[cfg(feature = "defmt")]
752 defmt::debug!("Disabling full modem");
753
754 at::send_at_blocking::<0>("AT+CFUN=0")?;
755 }
756 }
757
758 Ok(())
759 }
760}
761
762enum ModemActivation {
763 Lte,
764 Gnss,
765 Uicc,
766}
767
768impl ModemActivation {
769 async fn act_on_modem(&self) -> Result<(), Error> {
770 match self {
771 ModemActivation::Gnss => {
772 #[cfg(feature = "defmt")]
773 defmt::debug!("Enabling modem GNSS");
774
775 at::send_at::<0>("AT+CFUN=31").await?;
776 }
777 ModemActivation::Lte => {
778 #[cfg(feature = "defmt")]
779 defmt::debug!("Enabling modem LTE");
780
781 at::send_at::<0>("AT%XDATAPRFL=0").await?;
783 at::send_at::<0>("AT+CEPPI=1").await?;
785 at::send_at::<0>("AT+CFUN=21").await?;
787 }
788 ModemActivation::Uicc => {
789 #[cfg(feature = "defmt")]
790 defmt::debug!("Enabling UICC");
791
792 at::send_at::<0>("AT+CEPPI=1").await?;
794 at::send_at::<0>("AT+CFUN=41").await?;
796 }
797 }
798
799 Ok(())
800 }
801}