1#![no_std]
2#![doc = include_str!("../README.md")]
3pub use 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 fn init_with_custom_layout_core(
113 memory_layout: MemoryLayout,
114 #[cfg(feature = "os-irq")] os_irq: u8,
115) -> Result<(), Error> {
116 if INITIALIZED.fetch_or(true, Ordering::SeqCst) {
117 return Err(Error::ModemAlreadyInitialized);
118 }
119
120 #[cfg(feature = "os-irq")]
121 ffi::OS_IRQ.store(os_irq, Ordering::Relaxed);
122
123 const SHARED_MEMORY_RANGE: Range<u32> = 0x2000_0000..0x2002_0000;
124 const CTRL_SIZE: u32 = if cfg!(feature = "dect") {
125 nrfxlib_sys::NRF_MODEM_DECT_PHY_SHMEM_CTRL_SIZE
126 } else {
127 nrfxlib_sys::NRF_MODEM_CELLULAR_SHMEM_CTRL_SIZE
128 };
129
130 if !SHARED_MEMORY_RANGE.contains(&memory_layout.base_address) {
131 return Err(Error::BadMemoryLayout);
132 }
133
134 if !SHARED_MEMORY_RANGE.contains(
135 &(memory_layout.base_address
136 + CTRL_SIZE
137 + memory_layout.tx_area_size
138 + memory_layout.rx_area_size
139 + memory_layout.trace_area_size
140 - 1),
142 ) {
143 return Err(Error::BadMemoryLayout);
144 }
145
146 #[cfg(feature = "modem-trace")]
147 if memory_layout.trace_area_size == 0 {
148 return Err(Error::BadMemoryLayout);
149 }
150
151 unsafe {
153 (*pac::REGULATORS_NS::PTR)
154 .dcdcen
155 .modify(|_, w| w.dcdcen().enabled());
156 }
157
158 unsafe {
159 const HEAP_SIZE: usize = 1024;
160 static mut HEAP_MEMORY: [u32; HEAP_SIZE] = [0u32; HEAP_SIZE];
162 let heap_start = &raw mut HEAP_MEMORY;
163 let heap_size = HEAP_SIZE * core::mem::size_of::<u32>();
164 critical_section::with(|cs| {
165 *LIBRARY_ALLOCATOR.borrow(cs).borrow_mut() =
166 Some(Heap::new(heap_start.cast::<u8>(), heap_size))
167 });
168 }
169
170 static PARAMS: grounded::uninit::GroundedCell<nrfxlib_sys::nrf_modem_init_params> =
172 grounded::uninit::GroundedCell::uninit();
173
174 let params = nrfxlib_sys::nrf_modem_init_params {
175 shmem: nrfxlib_sys::nrf_modem_shmem_cfg {
176 ctrl: nrfxlib_sys::nrf_modem_shmem_cfg__bindgen_ty_1 {
177 base: memory_layout.base_address,
178 size: CTRL_SIZE,
179 },
180 tx: nrfxlib_sys::nrf_modem_shmem_cfg__bindgen_ty_2 {
181 base: memory_layout.base_address + CTRL_SIZE,
182 size: memory_layout.tx_area_size,
183 },
184 rx: nrfxlib_sys::nrf_modem_shmem_cfg__bindgen_ty_3 {
185 base: memory_layout.base_address + CTRL_SIZE + memory_layout.tx_area_size,
186 size: memory_layout.rx_area_size,
187 },
188 trace: nrfxlib_sys::nrf_modem_shmem_cfg__bindgen_ty_4 {
189 base: memory_layout.base_address
190 + CTRL_SIZE
191 + memory_layout.tx_area_size
192 + memory_layout.rx_area_size,
193 size: memory_layout.trace_area_size,
194 },
195 },
196 ipc_irq_prio: 0,
197 fault_handler: Some(modem_fault_handler),
198 dfu_handler: Some(modem_dfu_handler),
199 };
200
201 critical_section::with(|_| unsafe { PARAMS.get().write(params) });
202
203 unsafe {
204 critical_section::with(|cs| {
206 *TX_ALLOCATOR.borrow(cs).borrow_mut() = Some(Heap::new(
207 params.shmem.tx.base as usize as *mut u8,
208 params.shmem.tx.size as usize,
209 ))
210 });
211 }
212
213 unsafe { nrfxlib_sys::nrf_modem_init(PARAMS.get()) }.into_result()?;
215
216 Ok(())
217}
218
219pub async fn init_with_custom_layout(
224 mode: SystemMode,
225 memory_layout: MemoryLayout,
226 #[cfg(feature = "os-irq")] os_irq: u8,
227) -> Result<(), Error> {
228 init_with_custom_layout_core(
229 memory_layout,
230 #[cfg(feature = "os-irq")]
231 os_irq,
232 )?;
233
234 #[cfg(feature = "modem-trace")]
236 at::send_at::<0>("AT%XMODEMTRACE=1,2").await?;
237
238 at_notifications::initialize()?;
240
241 let (modem_state,) =
243 at_commands::parser::CommandParser::parse(at::send_at::<32>("AT+CFUN?").await?.as_bytes())
244 .expect_identifier(b"+CFUN: ")
245 .expect_int_parameter()
246 .expect_identifier(b"\r\nOK\r\n")
247 .finish()?;
248
249 if modem_state != 0 {
250 at::send_at::<0>("AT+CFUN=0").await?;
252 }
253
254 if !mode.is_valid_config() {
255 return Err(Error::InvalidSystemModeConfig);
256 }
257
258 let mut buffer = [0; 64];
259 let command = mode.create_at_command(&mut buffer)?;
260 at::send_at_bytes::<0>(command).await?;
261
262 mode.setup_psm().await?;
263
264 Ok(())
265}
266
267#[cfg(feature = "modem-trace")]
274pub async fn fetch_traces(cb: impl AsyncFn(&[u8])) -> Result<(), Error> {
275 let mut frags: *mut nrfxlib_sys::nrf_modem_trace_data = core::ptr::null_mut();
276 let mut nfrags = 0;
277
278 let res = unsafe {
279 nrfxlib_sys::nrf_modem_trace_get(
280 &mut frags,
281 &mut nfrags,
282 nrfxlib_sys::NRF_MODEM_OS_NO_WAIT as i32,
283 )
284 };
285
286 if res != 0 {
287 return Err(Error::NrfError(res as isize));
288 }
289
290 let frags = unsafe { core::slice::from_raw_parts(frags, nfrags) };
292 for nrfxlib_sys::nrf_modem_trace_data { data, len } in frags {
293 let data = unsafe { core::slice::from_raw_parts(*data as *mut u8, *len) };
294 cb(data).await;
295 unsafe {
296 nrfxlib_sys::nrf_modem_trace_processed(*len);
297 }
298 }
299
300 Ok(())
301}
302
303pub struct MemoryLayout {
310 pub base_address: u32,
312 pub tx_area_size: u32,
314 pub rx_area_size: u32,
316 pub trace_area_size: u32,
318}
319
320impl Default for MemoryLayout {
321 fn default() -> Self {
322 Self {
323 base_address: 0x2001_0000,
324 tx_area_size: 0x2000,
325 rx_area_size: 0x2000,
326 trace_area_size: if cfg!(feature = "modem-trace") {
327 0x2000
328 } else {
329 0
330 },
331 }
332 }
333}
334
335unsafe extern "C" fn modem_fault_handler(info: *mut nrfxlib_sys::nrf_modem_fault_info) {
336 #[cfg(feature = "defmt")]
337 defmt::panic!(
338 "Modem fault - reason: {}, pc: {}",
339 (*info).reason,
340 (*info).program_counter
341 );
342 #[cfg(not(feature = "defmt"))]
343 panic!(
344 "Modem fault - reason: {}, pc: {}",
345 (*info).reason,
346 (*info).program_counter
347 );
348}
349
350unsafe extern "C" fn modem_dfu_handler(_val: u32) {
351 #[cfg(feature = "defmt")]
352 defmt::trace!("Modem DFU handler");
353}
354
355pub fn ipc_irq_handler() {
357 unsafe {
358 crate::ffi::nrf_ipc_irq_handler();
359 }
360 cortex_m::asm::sev();
361}
362
363#[derive(Debug, Copy, Clone)]
367pub struct SystemMode {
368 pub lte_support: bool,
370 pub lte_psm_support: bool,
372 pub nbiot_support: bool,
374 pub gnss_support: bool,
376 pub preference: ConnectionPreference,
378}
379
380#[derive(Debug, Copy, Clone)]
382pub enum ConnectionPreference {
383 None = 0,
385 Lte = 1,
387 Nbiot = 2,
389 NetworkPreferenceWithLteFallback = 3,
391 NetworkPreferenceWithNbiotFallback = 4,
393}
394
395impl SystemMode {
396 fn is_valid_config(&self) -> bool {
397 if self.lte_psm_support && !self.lte_support {
398 return false;
399 }
400 match self.preference {
401 ConnectionPreference::None => true,
402 ConnectionPreference::Lte => self.lte_support,
403 ConnectionPreference::Nbiot => self.nbiot_support,
404 ConnectionPreference::NetworkPreferenceWithLteFallback => {
405 self.lte_support && self.nbiot_support
406 }
407 ConnectionPreference::NetworkPreferenceWithNbiotFallback => {
408 self.lte_support && self.nbiot_support
409 }
410 }
411 }
412
413 fn create_at_command<'a>(&self, buffer: &'a mut [u8]) -> Result<&'a [u8], Error> {
414 at_commands::builder::CommandBuilder::create_set(buffer, true)
415 .named("%XSYSTEMMODE")
416 .with_int_parameter(self.lte_support as u8)
417 .with_int_parameter(self.nbiot_support as u8)
418 .with_int_parameter(self.gnss_support as u8)
419 .with_int_parameter(self.preference as u8)
420 .finish()
421 .map_err(|e| Error::BufferTooSmall(Some(e)))
422 }
423
424 async fn setup_psm(&self) -> Result<(), Error> {
425 if self.lte_support {
426 if self.lte_psm_support {
427 at::send_at::<0>("AT+CPSMS=1").await?;
429 } else {
430 at::send_at::<0>("AT+CPSMS=0").await?;
432 }
433 }
434 Ok(())
435 }
436}
437
438#[cfg(feature = "nrf9160")]
446pub async fn configure_gnss_on_pca10090ns() -> Result<(), Error> {
447 #[cfg(feature = "defmt")]
448 defmt::debug!("Configuring XMAGPIO pins for 1574-1577 MHz");
449
450 crate::at::send_at::<0>("AT%XMAGPIO=1,0,0,1,1,1574,1577").await?;
452 Ok(())
453}
454
455struct RuntimeState {
456 state: embassy_sync::mutex::Mutex<CriticalSectionRawMutex, RuntimeStateInner>,
457 error: AtomicBool,
458}
459
460struct RuntimeStateInner {
461 gps_active: bool,
462 lte_link_count: u16,
463 uicc_link_count: u16,
464}
465
466impl RuntimeState {
467 const fn new() -> Self {
468 Self {
469 state: embassy_sync::mutex::Mutex::new(RuntimeStateInner {
470 gps_active: false,
471 lte_link_count: 0,
472 uicc_link_count: 0,
473 }),
474 error: AtomicBool::new(false),
475 }
476 }
477
478 pub(crate) async fn activate_gps(&self) -> Result<(), Error> {
479 let mut state = self.state.lock().await;
480
481 if state.gps_active {
482 return Err(Error::GnssAlreadyTaken);
483 }
484
485 ModemActivation::Gnss.act_on_modem().await?;
486
487 state.gps_active = true;
488
489 Ok(())
490 }
491
492 pub(crate) async fn deactivate_gps(&self) -> Result<(), Error> {
493 let mut state = self.state.lock().await;
494
495 if !state.gps_active {
496 panic!("Can't deactivate an inactive gps");
497 }
498
499 if state.lte_link_count == 0 && state.uicc_link_count == 0 {
500 ModemDeactivation::Everything.act_on_modem().await?;
501 } else {
502 ModemDeactivation::OnlyGnss.act_on_modem().await?;
503 }
504
505 state.gps_active = false;
506
507 Ok(())
508 }
509
510 pub(crate) fn deactivate_gps_blocking(&self) -> Result<(), Error> {
511 let mut state = self
512 .state
513 .try_lock()
514 .map_err(|_| Error::InternalRuntimeMutexLocked)?;
515
516 if !state.gps_active {
517 panic!("Can't deactivate an inactive gps");
518 }
519
520 if state.lte_link_count == 0 && state.uicc_link_count == 0 {
521 ModemDeactivation::Everything.act_on_modem_blocking()?;
522 } else {
523 ModemDeactivation::OnlyGnss.act_on_modem_blocking()?;
524 }
525
526 state.gps_active = false;
527
528 Ok(())
529 }
530
531 pub(crate) async fn activate_lte(&self) -> Result<(), Error> {
532 let mut state = self.state.lock().await;
533
534 if state.lte_link_count == u16::MAX {
535 return Err(Error::TooManyLteLinks);
536 }
537
538 if state.lte_link_count == 0 {
539 ModemActivation::Lte.act_on_modem().await?;
540 }
541
542 state.lte_link_count += 1;
543
544 Ok(())
545 }
546
547 pub(crate) async fn deactivate_lte(&self) -> Result<(), Error> {
548 let mut state = self.state.lock().await;
549
550 if state.lte_link_count == 0 {
551 panic!("Can't deactivate an inactive lte");
552 }
553
554 if state.lte_link_count == 1 {
555 if !state.gps_active && state.uicc_link_count == 0 {
556 ModemDeactivation::Everything.act_on_modem().await?;
557 } else {
558 ModemDeactivation::OnlyLte.act_on_modem().await?;
559 if state.uicc_link_count == 0 {
560 ModemDeactivation::OnlyUicc.act_on_modem().await?;
561 }
562 }
563 }
564
565 state.lte_link_count -= 1;
566
567 Ok(())
568 }
569
570 pub(crate) fn deactivate_lte_blocking(&self) -> Result<(), Error> {
571 let mut state = self
572 .state
573 .try_lock()
574 .map_err(|_| Error::InternalRuntimeMutexLocked)?;
575
576 if state.lte_link_count == 0 {
577 panic!("Can't deactivate an inactive lte");
578 }
579
580 if state.lte_link_count == 1 {
581 if !state.gps_active && state.uicc_link_count == 0 {
582 ModemDeactivation::Everything.act_on_modem_blocking()?;
583 } else {
584 ModemDeactivation::OnlyLte.act_on_modem_blocking()?;
585 if state.uicc_link_count == 0 {
586 ModemDeactivation::OnlyUicc.act_on_modem_blocking()?;
587 }
588 }
589 }
590
591 state.lte_link_count -= 1;
592
593 Ok(())
594 }
595
596 pub(crate) async fn activate_uicc(&self) -> Result<(), Error> {
597 let mut state = self.state.lock().await;
598
599 if state.uicc_link_count == u16::MAX {
600 return Err(Error::TooManyUiccLinks);
601 }
602
603 if state.uicc_link_count == 0 {
604 ModemActivation::Uicc.act_on_modem().await?;
605 }
606
607 state.uicc_link_count += 1;
608
609 Ok(())
610 }
611
612 pub(crate) async fn deactivate_uicc(&self) -> Result<(), Error> {
613 let mut state = self.state.lock().await;
614
615 if state.uicc_link_count == 0 {
616 panic!("Can't deactivate an inactive UICC");
617 }
618
619 if state.uicc_link_count == 1 {
620 if state.gps_active || state.lte_link_count > 0 {
621 ModemDeactivation::OnlyUicc
622 } else {
623 ModemDeactivation::Everything
624 }
625 } else {
626 ModemDeactivation::Nothing
627 }
628 .act_on_modem()
629 .await?;
630
631 state.uicc_link_count -= 1;
632
633 Ok(())
634 }
635
636 pub(crate) fn deactivate_uicc_blocking(&self) -> Result<(), Error> {
637 let mut state = self
638 .state
639 .try_lock()
640 .map_err(|_| Error::InternalRuntimeMutexLocked)?;
641
642 if state.uicc_link_count == 0 {
643 panic!("Can't deactivate an inactive UICC");
644 }
645
646 if state.uicc_link_count == 1 {
647 if state.gps_active || state.lte_link_count > 0 {
648 ModemDeactivation::OnlyUicc
649 } else {
650 ModemDeactivation::Everything
651 }
652 } else {
653 ModemDeactivation::Nothing
654 }
655 .act_on_modem_blocking()?;
656
657 state.uicc_link_count -= 1;
658
659 Ok(())
660 }
661
662 pub(crate) fn set_error_active(&self) {
663 self.error.store(true, Ordering::SeqCst);
664 }
665
666 pub(crate) fn get_error_active(&self) -> bool {
667 self.error.load(Ordering::SeqCst)
668 }
669
670 pub(crate) async unsafe fn reset_runtime_state(&self) -> Result<(), Error> {
671 let mut state = self.state.lock().await;
672
673 if self
674 .error
675 .compare_exchange(true, false, Ordering::SeqCst, Ordering::SeqCst)
676 .is_ok()
677 {
678 ModemDeactivation::Everything.act_on_modem().await?;
679 state.gps_active = false;
680 state.lte_link_count = 0;
681 state.uicc_link_count = 0;
682 }
683
684 self.error.store(false, Ordering::SeqCst);
685
686 Ok(())
687 }
688}
689
690pub fn has_runtime_state_error() -> bool {
695 MODEM_RUNTIME_STATE.get_error_active()
696}
697
698pub async unsafe fn reset_runtime_state() -> Result<(), Error> {
704 MODEM_RUNTIME_STATE.reset_runtime_state().await
705}
706
707enum ModemDeactivation {
708 OnlyGnss,
709 OnlyLte,
710 OnlyUicc,
711 Nothing,
712 Everything,
713}
714
715impl ModemDeactivation {
716 async fn act_on_modem(&self) -> Result<(), Error> {
717 match self {
718 ModemDeactivation::OnlyGnss => {
719 #[cfg(feature = "defmt")]
720 defmt::debug!("Disabling modem GNSS");
721
722 at::send_at::<0>("AT+CFUN=30").await?;
723 }
724 ModemDeactivation::OnlyLte => {
725 #[cfg(feature = "defmt")]
726 defmt::debug!("Disabling modem LTE");
727
728 at::send_at::<0>("AT+CFUN=20").await?;
730 }
732 ModemDeactivation::OnlyUicc => {
733 #[cfg(feature = "defmt")]
734 defmt::debug!("Disabling UICC");
735 at::send_at::<0>("AT+CFUN=40").await?;
737 }
738 ModemDeactivation::Nothing => {}
739 ModemDeactivation::Everything => {
740 #[cfg(feature = "defmt")]
741 defmt::debug!("Disabling full modem");
742
743 at::send_at::<0>("AT+CFUN=0").await?;
744 }
745 }
746
747 Ok(())
748 }
749
750 fn act_on_modem_blocking(&self) -> Result<(), Error> {
751 match self {
752 ModemDeactivation::OnlyGnss => {
753 #[cfg(feature = "defmt")]
754 defmt::debug!("Disabling modem GNSS");
755
756 at::send_at_blocking::<0>("AT+CFUN=30")?;
757 }
758 ModemDeactivation::OnlyLte => {
759 #[cfg(feature = "defmt")]
760 defmt::debug!("Disabling modem LTE");
761
762 at::send_at_blocking::<0>("AT+CFUN=20")?;
764 }
766 ModemDeactivation::OnlyUicc => {
767 #[cfg(feature = "defmt")]
768 defmt::debug!("Disabling UICC");
769 at::send_at_blocking::<0>("AT+CFUN=40")?;
771 }
772 ModemDeactivation::Nothing => {}
773 ModemDeactivation::Everything => {
774 #[cfg(feature = "defmt")]
775 defmt::debug!("Disabling full modem");
776
777 at::send_at_blocking::<0>("AT+CFUN=0")?;
778 }
779 }
780
781 Ok(())
782 }
783}
784
785enum ModemActivation {
786 Lte,
787 Gnss,
788 Uicc,
789}
790
791impl ModemActivation {
792 async fn act_on_modem(&self) -> Result<(), Error> {
793 match self {
794 ModemActivation::Gnss => {
795 #[cfg(feature = "defmt")]
796 defmt::debug!("Enabling modem GNSS");
797
798 at::send_at::<0>("AT+CFUN=31").await?;
799 }
800 ModemActivation::Lte => {
801 #[cfg(feature = "defmt")]
802 defmt::debug!("Enabling modem LTE");
803
804 at::send_at::<0>("AT%XDATAPRFL=0").await?;
806 at::send_at::<0>("AT+CEPPI=1").await?;
808 at::send_at::<0>("AT+CFUN=21").await?;
810 }
811 ModemActivation::Uicc => {
812 #[cfg(feature = "defmt")]
813 defmt::debug!("Enabling UICC");
814
815 at::send_at::<0>("AT+CEPPI=1").await?;
817 at::send_at::<0>("AT+CFUN=41").await?;
819 }
820 }
821
822 Ok(())
823 }
824}