Skip to main content

nrf_modem/
lib.rs

1#![no_std]
2#![doc = include_str!("../README.md")]
3// #![warn(missing_docs)]
4
5pub 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;
31/// Contains the core socket types and related functionality.
32pub 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
65/// We need to wrap our heap so it's creatable at run-time and accessible from an ISR.
66///
67/// * The Mutex allows us to safely share the heap between interrupt routines
68///   and the main thread - and nrfxlib will definitely use the heap in an
69///   interrupt.
70/// * The RefCell lets us share and object and mutate it (but not at the same
71///   time)
72/// * The Option is because the `linked_list_allocator::empty()` function is not
73///   `const` yet and cannot be called here
74///
75type WrappedHeap = Mutex<RefCell<Option<Heap>>>;
76
77/// Our general heap.
78///
79/// We initialise it later with a static variable as the backing store.
80static LIBRARY_ALLOCATOR: WrappedHeap = Mutex::new(RefCell::new(None));
81
82/// Our transmit heap.
83///
84/// We initalise this later using a special region of shared memory that can be
85/// seen by the Cortex-M33 and the modem CPU.
86static 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
91/// Start the NRF Modem library
92///
93/// With the os_irq feature enabled, you need to specify the OS scheduled IRQ number.
94/// The modem's IPC interrupt should be higher than the os irq. (IPC should pre-empt the executor)
95pub 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
105/// The core of [`init_with_custom_layout`] (and thus [`init`]).
106///
107/// This only runs the steps of setting up memory, DC/DC converter and running
108/// [`nrfxlib_sys::nrf_modem_init()`], but does not start exchanging AT commands, setting up AT
109/// notifications or setting a system mode.
110///
111/// This is mainly useful when working with the DECT modem firmware.
112pub 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                // Minus one, because this check should be inclusive
141                - 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    // The modem is only certified when the DC/DC converter is enabled and it isn't by default
152    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        /// Allocate some space in global data to use as a heap.
161        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    // Tell nrf_modem what memory it can use.
171    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        // Use the same TX memory region as above
205        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    // OK, let's start the library
214    unsafe { nrfxlib_sys::nrf_modem_init(PARAMS.get()) }.into_result()?;
215
216    Ok(())
217}
218
219/// Start the NRF Modem library with a manually specified memory layout
220///
221/// With the os_irq feature enabled, you need to specify the OS scheduled IRQ number.
222/// The modem's IPC interrupt should be higher than the os irq. (IPC should pre-empt the executor)
223pub 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    // Start tracing
235    #[cfg(feature = "modem-trace")]
236    at::send_at::<0>("AT%XMODEMTRACE=1,2").await?;
237
238    // Initialize AT notifications
239    at_notifications::initialize()?;
240
241    // Turn off the modem
242    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        // The modem is still turned on (probably from a previous run). Let's turn it off
251        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/// Fetch traces from the modem
268///
269/// Make sure to enable the `modem-trace` feature. Call this function regularly
270/// to ensure the trace buffer doesn't overflow.
271///
272/// `cb` will be called for every chunk of tracing data.
273#[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    // SAFETY: if nrf_modem_trace_get returns 0, frags is a valid pointer to the start of an array of size nfrags.
291    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
303/// The memory layout used by the modem library.
304///
305/// The full range needs to be in the lower 128k of ram.
306/// This also contains the fixed [nrfxlib_sys::NRF_MODEM_CELLULAR_SHMEM_CTRL_SIZE].
307///
308/// Nordic guide: <https://developer.nordicsemi.com/nRF_Connect_SDK/doc/2.4.1/nrfxlib/nrf_modem/doc/architecture.html#shared-memory-configuration>
309pub struct MemoryLayout {
310    /// The start of the memory area
311    pub base_address: u32,
312    /// The buffer size of the socket send operations, as well as sent AT commands and TLS certs
313    pub tx_area_size: u32,
314    /// The buffer size of the socket receive operations, as well as received AT commands, gnss messages and TLS certs
315    pub rx_area_size: u32,
316    /// The buffer size of the trace logs
317    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
355/// IPC code now lives outside `lib_modem`, so call our IPC handler function.
356pub fn ipc_irq_handler() {
357    unsafe {
358        crate::ffi::nrf_ipc_irq_handler();
359    }
360    cortex_m::asm::sev();
361}
362
363/// Identifies which radios in the nRF91* SiP should be active
364///
365/// Based on: <https://infocenter.nordicsemi.com/index.jsp?topic=%2Fref_at_commands%2FREF%2Fat_commands%2Fmob_termination_ctrl_status%2Fcfun.html>
366#[derive(Debug, Copy, Clone)]
367pub struct SystemMode {
368    /// Enables the modem to connect to the LTE network
369    pub lte_support: bool,
370    /// Enables the PowerSavingMode. You want this enabled unless your sim/network doesn't support it
371    pub lte_psm_support: bool,
372    /// Enables the modem to connect to the NBiot network
373    pub nbiot_support: bool,
374    /// Enables the modem to receive gnss signals
375    pub gnss_support: bool,
376    /// Sets up the preference the modem will have for connecting to the mobile network
377    pub preference: ConnectionPreference,
378}
379
380/// The preference the modem will have for connecting to the mobile network
381#[derive(Debug, Copy, Clone)]
382pub enum ConnectionPreference {
383    /// No preference. Initial system selection is based on history data and Universal Subscriber Identity Module (USIM)
384    None = 0,
385    /// LTE-M preferred
386    Lte = 1,
387    /// NB-IoT preferred
388    Nbiot = 2,
389    /// Network selection priorities override system priority, but if the same network or equal priority networks are found, LTE-M is preferred
390    NetworkPreferenceWithLteFallback = 3,
391    /// Network selection priorities override system priority, but if the same network or equal priority networks are found, NB-IoT is preferred
392    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                // Set Power Saving Mode (PSM)
428                at::send_at::<0>("AT+CPSMS=1").await?;
429            } else {
430                // Turn off PSM
431                at::send_at::<0>("AT+CPSMS=0").await?;
432            }
433        }
434        Ok(())
435    }
436}
437
438/// Enable GNSS on the nRF9160-DK (PCA10090NS)
439///
440/// Sends a AT%XMAGPIO command which activates the off-chip GNSS RF routing
441/// switch when receiving signals between 1574 MHz and 1577 MHz.
442///
443/// Works on the nRF9160-DK (PCA10090NS) and Actinius Icarus. Other PCBs may
444/// use different MAGPIO pins to control the GNSS switch.
445#[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    // Configure the GNSS antenna. See `nrf/samples/nrf9160/gps/src/main.c`.
451    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
690/// Returns true when the runtime has detected that its state may not represent the actual modem state.
691/// This means that the modem may remain active while the runtime thinks it has turned it off.
692///
693/// This can be fixed using [reset_runtime_state]
694pub fn has_runtime_state_error() -> bool {
695    MODEM_RUNTIME_STATE.get_error_active()
696}
697
698/// Resets the runtime state by forcing the modem to be turned off and resetting the state back to 0.
699///
700/// ## Safety
701///
702/// This function may only be used when you've made sure that **no** active LteLinks instances and Gnss instances exist
703pub 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                // Turn off the network side of the modem
729                at::send_at::<0>("AT+CFUN=20").await?;
730                // Do not turn of UICC, let the caller do that.
731            }
732            ModemDeactivation::OnlyUicc => {
733                #[cfg(feature = "defmt")]
734                defmt::debug!("Disabling UICC");
735                // Turn off the UICC
736                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                // Turn off the network side of the modem
763                at::send_at_blocking::<0>("AT+CFUN=20")?;
764                // Do not turn of UICC, let the caller do that.
765            }
766            ModemDeactivation::OnlyUicc => {
767                #[cfg(feature = "defmt")]
768                defmt::debug!("Disabling UICC");
769                // Turn off the UICC
770                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                // Set Ultra low power mode
805                at::send_at::<0>("AT%XDATAPRFL=0").await?;
806                // Set UICC low power mode
807                at::send_at::<0>("AT+CEPPI=1").await?;
808                // Activate LTE without changing GNSS
809                at::send_at::<0>("AT+CFUN=21").await?;
810            }
811            ModemActivation::Uicc => {
812                #[cfg(feature = "defmt")]
813                defmt::debug!("Enabling UICC");
814
815                // Set UICC low power mode
816                at::send_at::<0>("AT+CEPPI=1").await?;
817                // Activate LTE without changing GNSS
818                at::send_at::<0>("AT+CFUN=41").await?;
819            }
820        }
821
822        Ok(())
823    }
824}