nrf_modem/
lib.rs

1#![no_std]
2#![doc = include_str!("../README.md")]
3// #![warn(missing_docs)]
4
5use 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
56/// We need to wrap our heap so it's creatable at run-time and accessible from an ISR.
57///
58/// * The Mutex allows us to safely share the heap between interrupt routines
59///   and the main thread - and nrfxlib will definitely use the heap in an
60///   interrupt.
61/// * The RefCell lets us share and object and mutate it (but not at the same
62///   time)
63/// * The Option is because the `linked_list_allocator::empty()` function is not
64///   `const` yet and cannot be called here
65///
66type WrappedHeap = Mutex<RefCell<Option<Heap>>>;
67
68/// Our general heap.
69///
70/// We initialise it later with a static variable as the backing store.
71static LIBRARY_ALLOCATOR: WrappedHeap = Mutex::new(RefCell::new(None));
72
73/// Our transmit heap.
74///
75/// We initalise this later using a special region of shared memory that can be
76/// seen by the Cortex-M33 and the modem CPU.
77static 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
82/// Start the NRF Modem library
83///
84/// With the os_irq feature enabled, you need to specify the OS scheduled IRQ number.
85/// The modem's IPC interrupt should be higher than the os irq. (IPC should pre-empt the executor)
86pub 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
96/// Start the NRF Modem library with a manually specified memory layout
97///
98/// With the os_irq feature enabled, you need to specify the OS scheduled IRQ number.
99/// The modem's IPC interrupt should be higher than the os irq. (IPC should pre-empt the executor)
100pub 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                // Minus one, because this check should be inclusive
125                - 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    // The modem is only certified when the DC/DC converter is enabled and it isn't by default
136    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        /// Allocate some space in global data to use as a heap.
145        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    // Tell nrf_modem what memory it can use.
155    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        // Use the same TX memory region as above
191        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    // OK, let's start the library
200    unsafe { nrfxlib_sys::nrf_modem_init(PARAMS.get()) }.into_result()?;
201
202    // Start tracing
203    #[cfg(feature = "modem-trace")]
204    at::send_at::<0>("AT%XMODEMTRACE=1,2").await?;
205
206    // Initialize AT notifications
207    at_notifications::initialize()?;
208
209    // Turn off the modem
210    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        // The modem is still turned on (probably from a previous run). Let's turn it off
219        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/// Fetch traces from the modem
236///
237/// Make sure to enable the `modem-trace` feature. Call this function regularly
238/// to ensure the trace buffer doesn't overflow.
239///
240/// `cb` will be called for every chunk of tracing data.
241#[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    // SAFETY: if nrf_modem_trace_get returns 0, frags is a valid pointer to the start of an array of size nfrags.
259    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
271/// The memory layout used by the modem library.
272///
273/// The full range needs to be in the lower 128k of ram.
274/// This also contains the fixed [nrfxlib_sys::NRF_MODEM_CELLULAR_SHMEM_CTRL_SIZE].
275///
276/// Nordic guide: <https://developer.nordicsemi.com/nRF_Connect_SDK/doc/2.4.1/nrfxlib/nrf_modem/doc/architecture.html#shared-memory-configuration>
277pub struct MemoryLayout {
278    /// The start of the memory area
279    pub base_address: u32,
280    /// The buffer size of the socket send operations, as well as sent AT commands and TLS certs
281    pub tx_area_size: u32,
282    /// The buffer size of the socket receive operations, as well as received AT commands, gnss messages and TLS certs
283    pub rx_area_size: u32,
284    /// The buffer size of the trace logs
285    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
317/// IPC code now lives outside `lib_modem`, so call our IPC handler function.
318pub fn ipc_irq_handler() {
319    unsafe {
320        crate::ffi::nrf_ipc_irq_handler();
321    }
322    cortex_m::asm::sev();
323}
324
325/// Identifies which radios in the nRF91* SiP should be active
326///
327/// Based on: <https://infocenter.nordicsemi.com/index.jsp?topic=%2Fref_at_commands%2FREF%2Fat_commands%2Fmob_termination_ctrl_status%2Fcfun.html>
328#[derive(Debug, Copy, Clone)]
329pub struct SystemMode {
330    /// Enables the modem to connect to the LTE network
331    pub lte_support: bool,
332    /// Enables the PowerSavingMode. You want this enabled unless your sim/network doesn't support it
333    pub lte_psm_support: bool,
334    /// Enables the modem to connect to the NBiot network
335    pub nbiot_support: bool,
336    /// Enables the modem to receive gnss signals
337    pub gnss_support: bool,
338    /// Sets up the preference the modem will have for connecting to the mobile network
339    pub preference: ConnectionPreference,
340}
341
342/// The preference the modem will have for connecting to the mobile network
343#[derive(Debug, Copy, Clone)]
344pub enum ConnectionPreference {
345    /// No preference. Initial system selection is based on history data and Universal Subscriber Identity Module (USIM)
346    None = 0,
347    /// LTE-M preferred
348    Lte = 1,
349    /// NB-IoT preferred
350    Nbiot = 2,
351    /// Network selection priorities override system priority, but if the same network or equal priority networks are found, LTE-M is preferred
352    NetworkPreferenceWithLteFallback = 3,
353    /// Network selection priorities override system priority, but if the same network or equal priority networks are found, NB-IoT is preferred
354    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                // Set Power Saving Mode (PSM)
390                at::send_at::<0>("AT+CPSMS=1").await?;
391            } else {
392                // Turn off PSM
393                at::send_at::<0>("AT+CPSMS=0").await?;
394            }
395        }
396        Ok(())
397    }
398}
399
400/// Enable GNSS on the nRF9160-DK (PCA10090NS)
401///
402/// Sends a AT%XMAGPIO command which activates the off-chip GNSS RF routing
403/// switch when receiving signals between 1574 MHz and 1577 MHz.
404///
405/// Works on the nRF9160-DK (PCA10090NS) and Actinius Icarus. Other PCBs may
406/// use different MAGPIO pins to control the GNSS switch.
407#[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    // Configure the GNSS antenna. See `nrf/samples/nrf9160/gps/src/main.c`.
413    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
576/// Returns true when the runtime has detected that its state may not represent the actual modem state.
577/// This means that the modem may remain active while the runtime thinks it has turned it off.
578///
579/// This can be fixed using [reset_runtime_state]
580pub fn has_runtime_state_error() -> bool {
581    MODEM_RUNTIME_STATE.get_error_active()
582}
583
584/// Resets the runtime state by forcing the modem to be turned off and resetting the state back to 0.
585///
586/// ## Safety
587///
588/// This function may only be used when you've made sure that **no** active LteLinks instances and Gnss instances exist
589pub 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                // Turn off the network side of the modem
614                at::send_at::<0>("AT+CFUN=20").await?;
615                // Turn off the UICC
616                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                // Turn off the network side of the modem
643                at::send_at_blocking::<0>("AT+CFUN=20")?;
644                // Turn off the UICC
645                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                // Set Ultra low power mode
679                at::send_at::<0>("AT%XDATAPRFL=0").await?;
680                // Set UICC low power mode
681                at::send_at::<0>("AT+CEPPI=1").await?;
682                // Activate LTE without changing GNSS
683                at::send_at::<0>("AT+CFUN=21").await?;
684            }
685        }
686
687        Ok(())
688    }
689}