stm32cubeprogrammer/
cube_programmer.rs

1use crate::{
2    api_log, api_types, display,
3    error::{CubeProgrammerError, CubeProgrammerResult},
4    utility,
5};
6use bon::bon;
7use derive_more::Into;
8use log::{debug, error};
9use std::{
10    cell::RefCell,
11    collections::HashMap,
12    sync::{Arc, Mutex},
13};
14use stm32cubeprogrammer_sys::libloading;
15use stm32cubeprogrammer_sys::SRAM_BASE_ADDRESS;
16
17macro_rules! verify_api_struct {
18    ($api:expr, $($field:ident),*) => {{
19        $(
20            match &$api.$field {
21                Ok(_) => (), // Symbol exists, continue
22                Err(err) => return Err(CubeProgrammerError::MissingDllSymbol{message: format!(
23                    "Missing symbol '{}': {}", stringify!($field), err
24                )}),
25            }
26        )*
27        Ok(())
28    }};
29}
30
31/// HashMap to store connected probes.
32/// The key is the serial number of the probe.
33/// The value is an option of a probe: A probe is only usable for one target connection at a time. If a connection is established, the probe is taken from the hashmap. If a connection is closed, the probe is returned to the hashmap.
34type ProbeRegistry = HashMap<crate::probe::Serial, Option<crate::probe::Probe>>;
35
36/// Central struct to interact with the underlying CubeProgrammer API library. Factory for connections.
37/// Multiple connections are possible at the same time, if multiple probes are connected.
38pub struct CubeProgrammer {
39    /// API
40    api: stm32cubeprogrammer_sys::CubeProgrammer_API,
41
42    /// Registry of probes
43    probe_registry: RefCell<ProbeRegistry>,
44}
45
46/// Programmer connected to the target which is created via calling [`CubeProgrammer::connect_to_target`] on the CubeProgrammer
47#[derive(Debug)]
48pub struct ConnectedProgrammer<'a> {
49    /// Reference to the CubeProgrammer for api access and reinsertion of the probe into the probe registry
50    programmer: &'a CubeProgrammer,
51    /// Connected probe. The probe is taken from the probe registry and reinserted after the connection is closed
52    probe: crate::probe::Probe,
53    /// General information about the connected target which is retrieved after the connection is established
54    general_information: api_types::GeneralInformation,
55}
56
57/// Programmer connected to the target FUS (firmware update service) which is created via calling [`CubeProgrammer::connect_to_target_fus`]
58#[derive(Debug)]
59pub struct ConnectedFusProgrammer<'a> {
60    programmer: ConnectedProgrammer<'a>,
61    fus_info: crate::fus::Information,
62}
63
64#[bon]
65impl CubeProgrammer {
66    /// Create new instance
67    /// - Load the CubeProgrammer API library (sys crate)
68    /// - Set the verbosity level
69    /// - Set the display callback handler
70    /// - Set the loader path
71    #[builder]
72    pub fn new(
73        cube_programmer_dir: &impl AsRef<std::path::Path>,
74        log_verbosity: Option<api_log::Verbosity>,
75        display_callback: Option<Arc<Mutex<dyn crate::DisplayCallback>>>,
76    ) -> Result<Self, CubeProgrammerError> {
77        use stm32cubeprogrammer_sys::{PATH_API_LIBRARY_RELATIVE, PATH_LOADER_DIR_RELATIVE};
78
79        let api_path = cube_programmer_dir
80            .as_ref()
81            .join(PATH_API_LIBRARY_RELATIVE)
82            .canonicalize()
83            .map_err(CubeProgrammerError::FileIo)?;
84
85        let loader_path = cube_programmer_dir
86            .as_ref()
87            .join(PATH_LOADER_DIR_RELATIVE)
88            .canonicalize()
89            .map_err(CubeProgrammerError::FileIo)?;
90
91        debug!("API path: {:?}", api_path);
92        debug!("Loader path: {:?}", loader_path);
93
94        let library = Self::load_library(&api_path).map_err(CubeProgrammerError::LibLoading)?;
95
96        let api = unsafe {
97            stm32cubeprogrammer_sys::CubeProgrammer_API::from_library(library)
98                .map_err(CubeProgrammerError::LibLoading)?
99        };
100
101        // Verify if all API functions are available before proceeding
102        verify_api_struct!(
103            api,
104            setVerbosityLevel,
105            setDisplayCallbacks,
106            setLoadersPath,
107            getStLinkList,
108            deleteInterfaceList,
109            connectStLink,
110            getDeviceGeneralInf,
111            disconnect,
112            startFus,
113            reset,
114            downloadFile,
115            massErase,
116            saveMemoryToFile,
117            sendOptionBytesCmd,
118            readUnprotect,
119            checkDeviceConnection,
120            readMemory,
121            freeLibraryMemory,
122            startWirelessStack,
123            writeCortexRegistres,
124            readCortexReg,
125            firmwareDelete,
126            firmwareUpgrade
127        )?;
128
129        if let Some(display_callback) = display_callback {
130            debug!("Set display callback handler");
131            display::set_display_callback_handler(display_callback);
132        }
133
134        unsafe {
135            {
136                let verbosity = log_verbosity.unwrap_or({
137                    debug!("Use default verbosity level");
138                    api_log::Verbosity::Level3
139                });
140
141                debug!("Set verbosity level: {}", verbosity);
142
143                api.setVerbosityLevel(verbosity.into());
144            }
145
146            let display_callbacks = stm32cubeprogrammer_sys::displayCallBacks {
147                initProgressBar: Some(api_log::display_callback_init_progressbar),
148                logMessage: Some(api_log::display_callback_log_message),
149                loadBar: Some(api_log::display_callback_load_bar),
150            };
151
152            api.setDisplayCallbacks(display_callbacks);
153            api.setLoadersPath(utility::path_to_cstring(loader_path)?.as_ptr());
154        }
155
156        Ok(Self {
157            api,
158            probe_registry: RefCell::new(HashMap::new()),
159        })
160    }
161
162    /// Scan for connected probes and sync the probe registry with the scan result
163    /// If a probe is already in use, the related entry is not changed
164    fn scan_for_probes(&self) -> CubeProgrammerResult<()> {
165        let mut debug_parameters =
166            std::ptr::null_mut::<stm32cubeprogrammer_sys::debugConnectParameters>();
167        let return_value = unsafe { self.api.getStLinkList(&mut debug_parameters, 0) };
168
169        if return_value < 0 || debug_parameters.is_null() {
170            return Err(CubeProgrammerError::ActionOutputUnexpected {
171                action: crate::error::Action::ListConnectedProbes,
172                unexpected_output: crate::error::UnexpectedOutput::Null,
173            });
174        }
175
176        let slice = unsafe {
177            std::slice::from_raw_parts(
178                debug_parameters as *mut crate::probe::Probe,
179                return_value as _,
180            )
181        };
182
183        let mut connected_probes = self.probe_registry.borrow_mut();
184
185        // Delete all entries where the value is not None -> There is no active connection
186        connected_probes.retain(|_, value| value.is_none());
187
188        for probe in slice {
189            // Only insert if the key is not already present
190            connected_probes
191                .entry(probe.serial_number().to_string().into())
192                .or_insert_with(|| Some(probe.clone()));
193        }
194
195        // Free the memory allocated by the API
196        unsafe {
197            self.api.deleteInterfaceList();
198        }
199
200        Ok(())
201    }
202
203    /// List available probes. Scans for connected probes internally and returns the serial numbers of the connected probes which are not currently in use
204    pub fn list_available_probes(&self) -> CubeProgrammerResult<Vec<crate::probe::Serial>> {
205        self.scan_for_probes()?;
206
207        let connected_probes = self.probe_registry.borrow();
208
209        Ok(connected_probes
210            .values()
211            .filter_map(|probe| {
212                probe
213                    .as_ref()
214                    .map(|probe| probe.serial_number().to_string().into())
215            })
216            .collect())
217    }
218
219    /// Insert a probe into the probe registry. Is called in the drop implementation of [`ConnectedProgrammer``]
220    fn insert_probe(&self, probe: &crate::probe::Probe) {
221        let mut connected_probes = self.probe_registry.borrow_mut();
222        connected_probes.insert(probe.serial_number().to_owned().into(), Some(probe.clone()));
223    }
224
225    /// Connect to a target via a given probe
226    pub fn connect_to_target(
227        &self,
228        probe_serial_number: &crate::probe::Serial,
229        protocol: &crate::probe::Protocol,
230        connection_parameters: &crate::probe::ConnectionParameters,
231    ) -> CubeProgrammerResult<ConnectedProgrammer> {
232        let mut connected_probes = self.probe_registry.borrow_mut();
233
234        if let Some(probe) = connected_probes.get_mut(probe_serial_number) {
235            if let Some(inner) = probe.take() {
236                // Try to connect to the target with the probe
237                match api_types::ReturnCode::<0>::from(unsafe {
238                    self.api.connectStLink(*crate::probe::Probe::new(
239                        &inner,
240                        protocol,
241                        connection_parameters,
242                    ))
243                })
244                .check(crate::error::Action::Connect)
245                {
246                    Ok(_) => {
247                        // Try to get the general device information
248                        let general_information = unsafe { self.api.getDeviceGeneralInf() };
249                        if general_information.is_null() {
250                            // Reinsert the probe into the connected_probes HashMap
251                            *probe = Some(inner);
252
253                            unsafe { self.api.disconnect() };
254
255                            return Err(CubeProgrammerError::ActionOutputUnexpected {
256                                action: crate::error::Action::ReadTargetInfo,
257                                unexpected_output: crate::error::UnexpectedOutput::Null,
258                            });
259                        }
260
261                        // We could connect and get the general information
262                        let general_information =
263                            api_types::GeneralInformation::from(unsafe { *general_information });
264
265                        Ok(ConnectedProgrammer {
266                            programmer: self,
267                            probe: inner,
268                            general_information,
269                        })
270                    }
271                    Err(e) => {
272                        error!(
273                            "Cannot connect to target via probe with serial number: {}",
274                            probe_serial_number
275                        );
276
277                        // Reinsert the probe into the connected_probes HashMap
278                        *probe = Some(inner);
279
280                        Err(e)
281                    }
282                }
283            } else {
284                Err(CubeProgrammerError::Parameter {
285                    action: crate::error::Action::Connect,
286                    message: format!(
287                        "Probe with serial number {} already in use",
288                        probe_serial_number
289                    ),
290                })
291            }
292        } else {
293            Err(CubeProgrammerError::Parameter {
294                action: crate::error::Action::Connect,
295                message: format!("Probe with serial number {} not found", probe_serial_number),
296            })
297        }
298    }
299
300    /// Connect to the firmware update service (FUS) of a target via a given probe
301    /// No custom connection parameters can be specified, as a special [connection procedure](https://wiki.st.com/stm32mcu/wiki/Connectivity:STM32WB_FUS) is necessary to access the FUS info table:
302    /// - Disconnect
303    /// - Connect (mode: normal ; reset: hardware)
304    /// - Start FUS
305    /// - Disconnect
306    /// - Connect (mode: normal ; reset: hot-plug)
307    pub fn connect_to_target_fus(
308        &self,
309        probe_serial_number: &crate::probe::Serial,
310        protocol: &crate::probe::Protocol,
311    ) -> CubeProgrammerResult<ConnectedFusProgrammer> {
312        // Connect with hardware reset an normal mode
313        let connected = self.connect_to_target(
314            probe_serial_number,
315            protocol,
316            &crate::probe::ConnectionParameters {
317                frequency: crate::probe::Frequency::Highest,
318                reset_mode: crate::probe::ResetMode::Hardware,
319                connection_mode: crate::probe::ConnectionMode::Normal,
320            },
321        )?;
322
323        connected.check_fus_support()?;
324
325        // Start the FUS
326        api_types::ReturnCode::<1>::from(unsafe { connected.api().startFus() })
327            .check(crate::error::Action::StartFus)?;
328
329        // Disconnect
330        connected.disconnect();
331
332        // Reconnect with hot plug
333        let connected = self.connect_to_target(
334            probe_serial_number,
335            protocol,
336            &crate::probe::ConnectionParameters {
337                frequency: crate::probe::Frequency::Highest,
338                reset_mode: crate::probe::ResetMode::Hardware,
339                connection_mode: crate::probe::ConnectionMode::HotPlug,
340            },
341        )?;
342
343        // Read the FUS information
344        let fus_info = connected.read_fus_info()?;
345
346        Ok(ConnectedFusProgrammer {
347            programmer: connected,
348            fus_info,
349        })
350    }
351
352    /// Load the dynamic library with libloading
353    fn load_library(
354        api_library_path: impl AsRef<std::ffi::OsStr>,
355    ) -> Result<libloading::Library, libloading::Error> {
356        #[cfg(windows)]
357        unsafe fn load_inner(
358            path: impl AsRef<std::ffi::OsStr>,
359        ) -> Result<libloading::Library, libloading::Error> {
360            let library: libloading::Library = unsafe {
361                libloading::os::windows::Library::load_with_flags(
362                    path,
363                    libloading::os::windows::LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR
364                        | libloading::os::windows::LOAD_LIBRARY_SEARCH_SYSTEM32
365                        | libloading::os::windows::LOAD_LIBRARY_SEARCH_DEFAULT_DIRS,
366                )?
367                .into()
368            };
369
370            Ok(library)
371        }
372
373        #[cfg(unix)]
374        unsafe fn load_inner(
375            path: impl AsRef<std::ffi::OsStr>,
376        ) -> Result<libloading::Library, libloading::Error> {
377            use stm32cubeprogrammer_sys::libloading;
378
379            let library: libloading::Library =
380                unsafe { libloading::os::unix::Library::new(path)?.into() };
381
382            Ok(library)
383        }
384
385        unsafe { load_inner(api_library_path.as_ref()) }
386    }
387}
388
389impl std::fmt::Debug for CubeProgrammer {
390    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
391        f.debug_struct("CubeProgrammerApi").finish_non_exhaustive()
392    }
393}
394
395impl Drop for ConnectedProgrammer<'_> {
396    /// Disconnect and re-insert the probe into the probe registry of the api
397    fn drop(&mut self) {
398        unsafe {
399            self.api().disconnect();
400        }
401
402        self.programmer.insert_probe(&self.probe);
403    }
404}
405
406impl ConnectedProgrammer<'_> {
407    /// Disconnect from target
408    pub fn disconnect(self) {
409        // Consume self -> Drop is called to disconnect
410    }
411
412    /// Get general device information
413    pub fn general_information(&self) -> &api_types::GeneralInformation {
414        &self.general_information
415    }
416
417    fn api(&self) -> &stm32cubeprogrammer_sys::CubeProgrammer_API {
418        &self.programmer.api
419    }
420
421    fn check_fus_support(&self) -> CubeProgrammerResult<()> {
422        if !self.general_information.fus_support {
423            return Err(CubeProgrammerError::ActionNotSupported {
424                action: crate::error::Action::StartFus,
425                message: format!(
426                    "Connection target {} does not support FUS",
427                    self.general_information.name
428                ),
429            });
430        }
431
432        Ok(())
433    }
434
435    /// Reads the firmware update service (FUS) information from the shared SRAM2A
436    fn read_fus_info(&self) -> CubeProgrammerResult<crate::fus::Information> {
437        /// Helper function to convert the version word to major, minor, subs
438        fn u32_to_version(version: u32) -> crate::fus::Version {
439            const INFO_VERSION_MAJOR_OFFSET: u32 = 24;
440            const INFO_VERSION_MAJOR_MASK: u32 = 0xff000000;
441            const INFO_VERSION_MINOR_OFFSET: u32 = 16;
442            const INFO_VERSION_MINOR_MASK: u32 = 0x00ff0000;
443            const INFO_VERSION_SUB_OFFSET: u32 = 8;
444            const INFO_VERSION_SUB_MASK: u32 = 0x0000ff00;
445            const INFO_VERSION_TYPE_OFFSET: u32 = 0;
446            const INFO_VERSION_TYPE_MASK: u32 = 0x00000000f;
447
448            crate::fus::Version {
449                major: ((version & INFO_VERSION_MAJOR_MASK) >> INFO_VERSION_MAJOR_OFFSET) as u8,
450                minor: ((version & INFO_VERSION_MINOR_MASK) >> INFO_VERSION_MINOR_OFFSET) as u8,
451                sub: ((version & INFO_VERSION_SUB_MASK) >> INFO_VERSION_SUB_OFFSET) as u8,
452                r#type: Some(
453                    ((version & INFO_VERSION_TYPE_MASK) >> INFO_VERSION_TYPE_OFFSET) as u8,
454                ),
455            }
456        }
457
458        /// Offsets of the FUS device info table fields
459        const DEVICE_INFO_TABLE_STATE_OFFSET: u32 = 0;
460        // const RESERVED1_OFFSET: u32 = 4;
461        // const LAST_FUS_ACTIVE_STATE_OFFSET: u32 = 5;
462        // const LAST_WIRELESS_STACK_STATE_OFFSET: u32 = 6;
463        // const CURRENT_WIRELESS_STACK_TYPE_OFFSET: u32 = 7;
464        // const SAFE_BOOT_VERSION_OFFSET: u32 = 8;
465        const FUS_VERSION_OFFSET: u32 = 12;
466        // const FUS_MEMORY_SIZE_OFFSET: u32 = 16;
467        const WIRELESS_STACK_VERSION_OFFSET: u32 = 20;
468        // const WIRELESS_STACK_MEMORY_SIZE_OFFSET: u32 = 24;
469        // const WIRELESS_FIRMWARE_BLE_INFO_OFFSET: u32 = 28;
470        // const WIRELESS_FIRMWARE_THREAD_INFO_OFFSET: u32 = 32;
471        // const RESERVED2_OFFSET: u32 = 36;
472        const UID64_OFFSET: u32 = 40;
473        const DEVICE_ID_OFFSET: u32 = 48;
474
475        /// Keyword to check if the FUS device info table is valid
476        const FUS_DEVICE_INFO_TABLE_VALIDITY_KEYWORD: u32 = 0xA94656B9;
477        /// Offset of the shared RAM
478        const SRAM2A_BASE_ADDRESS: u32 = SRAM_BASE_ADDRESS + 0x00030000;
479
480        let info_table_address = self.read_memory::<u32>(SRAM2A_BASE_ADDRESS, 1)?[0];
481
482        if info_table_address == 0 {
483            return Err(CubeProgrammerError::ActionOutputUnexpected {
484                action: crate::error::Action::ReadFusInfo,
485                unexpected_output: crate::error::UnexpectedOutput::Null,
486            });
487        }
488
489        let device_info_table_state =
490            self.read_memory::<u32>(info_table_address + DEVICE_INFO_TABLE_STATE_OFFSET, 1)?[0];
491
492        let fus_version = self.read_memory::<u32>(info_table_address + FUS_VERSION_OFFSET, 1)?[0];
493
494        let wireless_stack_version =
495            self.read_memory::<u32>(info_table_address + WIRELESS_STACK_VERSION_OFFSET, 1)?[0];
496
497        let uid64 = self.read_memory::<u64>(info_table_address + UID64_OFFSET, 1)?[0];
498
499        let device_id = self.read_memory::<u16>(info_table_address + DEVICE_ID_OFFSET, 1)?[0];
500
501        if device_info_table_state != FUS_DEVICE_INFO_TABLE_VALIDITY_KEYWORD {
502            error!("Read FUS info table is not valid. Return default FUS info");
503            return Err(CubeProgrammerError::ActionOutputUnexpected {
504                action: crate::error::Action::ReadFusInfo,
505                unexpected_output: crate::error::UnexpectedOutput::Null,
506            });
507        }
508
509        Ok(crate::fus::Information {
510            fus_version: u32_to_version(fus_version),
511            wireless_stack_version: u32_to_version(wireless_stack_version),
512            device_id,
513            uid64,
514        })
515    }
516
517    /// Reset target
518    pub fn reset_target(&self, reset_mode: crate::probe::ResetMode) -> CubeProgrammerResult<()> {
519        self.check_connection()?;
520        api_types::ReturnCode::<0>::from(unsafe { self.api().reset(reset_mode.into()) })
521            .check(crate::error::Action::Reset)
522    }
523
524    /// Download hex file to target
525    pub fn download_hex_file(
526        &self,
527        file_path: impl AsRef<std::path::Path>,
528        skip_erase: bool,
529        verify: bool,
530    ) -> CubeProgrammerResult<()> {
531        // Validate if the given file is a valid hex file if the feature is enabled
532        #[cfg(feature = "ihex")]
533        {
534            // Check if the given file is really a hex file
535            // Unfortunately, the CubeProgrammer API does not check this and simply programs to address 0 if a bin file is passed
536            let file_content = std::fs::read(&file_path).map_err(CubeProgrammerError::FileIo)?;
537            let file_content =
538                std::str::from_utf8(&file_content).map_err(|_| CubeProgrammerError::Parameter {
539                    action: crate::error::Action::DownloadFile,
540                    message: "Invalid intelhex file".to_string(),
541                })?;
542
543            let reader = ihex::Reader::new_with_options(
544                file_content,
545                ihex::ReaderOptions {
546                    stop_after_first_error: true,
547                    stop_after_eof: true,
548                },
549            );
550
551            for record in reader {
552                match record {
553                    Ok(_) => {}
554                    Err(e) => {
555                        return Err(CubeProgrammerError::Parameter {
556                            action: crate::error::Action::DownloadFile,
557                            message: format!("Invalid intelhex file: {}", e),
558                        });
559                    }
560                }
561            }
562        }
563
564        self.check_connection()?;
565
566        let file_path = utility::path_to_widestring(file_path);
567
568        api_types::ReturnCode::<0>::from(unsafe {
569            self.api().downloadFile(
570                file_path?.as_ptr(),
571                0,
572                if skip_erase { 1 } else { 0 },
573                if verify { 1 } else { 0 },
574                std::ptr::null(),
575            )
576        })
577        .check(crate::error::Action::DownloadFile)
578    }
579
580    /// Download binary file to target
581    pub fn download_bin_file(
582        &self,
583        file_path: impl AsRef<std::path::Path>,
584        start_address: u32,
585        skip_erase: bool,
586        verify: bool,
587    ) -> CubeProgrammerResult<()> {
588        self.check_connection()?;
589
590        let file_path = utility::path_to_widestring(file_path);
591
592        api_types::ReturnCode::<0>::from(unsafe {
593            self.api().downloadFile(
594                file_path?.as_ptr(),
595                start_address,
596                if skip_erase { 1 } else { 0 },
597                if verify { 1 } else { 0 },
598                std::ptr::null(),
599            )
600        })
601        .check(crate::error::Action::DownloadFile)
602    }
603
604    /// Perform mass erase
605    pub fn mass_erase(&self) -> CubeProgrammerResult<()> {
606        self.check_connection()?;
607
608        api_types::ReturnCode::<0>::from(unsafe { self.api().massErase(std::ptr::null_mut()) })
609            .check(crate::error::Action::MassErase)
610    }
611
612    /// Save memory to file
613    /// Attention: The file path must end with .hex or .bin
614    pub fn save_memory(
615        &self,
616        file_path: impl AsRef<std::path::Path>,
617        start_address: u32,
618        size_bytes: u32,
619    ) -> CubeProgrammerResult<()> {
620        self.check_connection()?;
621
622        api_types::ReturnCode::<0>::from(unsafe {
623            self.api().saveMemoryToFile(
624                i32::try_from(start_address).map_err(|x| CubeProgrammerError::Parameter {
625                    action: crate::error::Action::SaveMemory,
626                    message: format!("Start address exceeds max value: {}", x),
627                })?,
628                i32::try_from(size_bytes).map_err(|x| CubeProgrammerError::Parameter {
629                    action: crate::error::Action::SaveMemory,
630                    message: format!("Size exceeds max value: {}", x),
631                })?,
632                utility::path_to_widestring(file_path)?.as_ptr(),
633            )
634        })
635        .check(crate::error::Action::SaveMemory)
636    }
637
638    /// Enable roud out protection level 1 (0xBB)
639    pub fn enable_read_out_protection(&self) -> CubeProgrammerResult<()> {
640        /// Command according to Example 3 of the CubeProgrammer API documentation
641        const COMMAND_ENABLE_ROP_LEVEL_1: &str = "-ob rdp=0xbb";
642
643        self.check_connection()?;
644
645        api_types::ReturnCode::<0>::from(unsafe {
646            self.api().sendOptionBytesCmd(
647                utility::string_to_cstring(COMMAND_ENABLE_ROP_LEVEL_1)?.as_ptr()
648                    as *mut std::ffi::c_char,
649            )
650        })
651        .check(crate::error::Action::EnableReadOutProtection)
652    }
653
654    /// Disable read out protection
655    /// Attention: This command will eOrase the device memory
656    pub fn disable_read_out_protection(&self) -> CubeProgrammerResult<()> {
657        self.check_connection()?;
658        api_types::ReturnCode::<0>::from(unsafe { self.api().readUnprotect() })
659            .check(crate::error::Action::DisableReadOutProtection)?;
660        Ok(())
661    }
662
663    /// Check connection to target
664    /// Consumes self and and only returns self if the connection is still maintained
665    /// If the connection is lost, the user is forced to reconnect
666    fn check_connection(&self) -> CubeProgrammerResult<()> {
667        api_types::ReturnCode::<1>::from(unsafe { self.api().checkDeviceConnection() })
668            .check(crate::error::Action::CheckConnection)
669    }
670
671    /// Read memory as struct
672    /// The struct needs to support the traits `bytemuck::Pod` and `bytemuck::Zeroable`
673    /// These traits are implemented for lots of types e.g. (full list available [here](https://docs.rs/bytemuck/1.21.0/bytemuck/trait.Pod.html)):
674    /// - u8, u16, u32
675    /// - i8, i16, i32
676    /// - f32
677    ///
678    /// # Arguments
679    /// address: Start address to read from
680    /// count: Number of struct elements to read
681    pub fn read_memory<T: bytemuck::Pod + bytemuck::Zeroable>(
682        &self,
683        address: u32,
684        count: usize,
685    ) -> CubeProgrammerResult<Vec<T>> {
686        let size = u32::try_from(std::mem::size_of::<T>() * count).map_err(|x| {
687            CubeProgrammerError::Parameter {
688                action: crate::error::Action::ReadMemory,
689                message: format!("Size exceeds max value: {}", x),
690            }
691        })?;
692
693        let mut data = std::ptr::null_mut();
694
695        api_types::ReturnCode::<0>::from(unsafe {
696            self.api().readMemory(address, &mut data, size)
697        })
698        .check(crate::error::Action::ReadMemory)?;
699
700        if data.is_null() {
701            return Err(CubeProgrammerError::ActionOutputUnexpected {
702                action: crate::error::Action::ReadMemory,
703                unexpected_output: crate::error::UnexpectedOutput::Null,
704            });
705        }
706
707        let pod_data: &[T] =
708            bytemuck::try_cast_slice(unsafe { std::slice::from_raw_parts(data, size as _) })
709                .map_err(|_| CubeProgrammerError::ActionOutputUnexpected {
710                    action: crate::error::Action::ReadMemory,
711                    unexpected_output: crate::error::UnexpectedOutput::SliceConversion,
712                })?;
713
714        let pod_data = pod_data.to_vec();
715
716        unsafe {
717            self.api().freeLibraryMemory(data as *mut std::ffi::c_void);
718        }
719
720        if pod_data.len() != count {
721            return Err(CubeProgrammerError::ActionOutputUnexpected {
722                action: crate::error::Action::ReadMemory,
723                unexpected_output: crate::error::UnexpectedOutput::SliceLength,
724            });
725        }
726
727        Ok(pod_data)
728    }
729
730    /// Write memory as struct
731    /// The struct needs to support the traits `bytemuck::Pod` and `bytemuck::Zeroable`
732    /// These traits are implemented for lots of types e.g. (full list available [here](https://docs.rs/bytemuck/1.21.0/bytemuck/trait.Pod.html)):
733    /// - u8, u16, u32
734    /// - i8, i16, i32
735    /// - f32
736    ///
737    /// # Arguments
738    /// address: Start address to write to
739    /// data: A slice of struct elements to write
740    pub fn write_memory<T: bytemuck::Pod + std::fmt::Debug>(
741        &self,
742        address: u32,
743        data: &[T],
744    ) -> CubeProgrammerResult<()> {
745        let size = u32::try_from(std::mem::size_of_val(data)).map_err(|x| {
746            CubeProgrammerError::Parameter {
747                action: crate::error::Action::WriteMemory,
748                message: format!("Size exceeds max value: {}", x),
749            }
750        })?;
751
752        let mut bytes = data
753            .iter()
754            .flat_map(|x| bytemuck::bytes_of(x).to_vec())
755            .collect::<Vec<_>>();
756
757        api_types::ReturnCode::<0>::from(unsafe {
758            self.api()
759                .writeMemory(address, bytes.as_mut_ptr() as *mut i8, size)
760        })
761        .check(crate::error::Action::WriteMemory)
762    }
763
764    /// Start the wireless stack
765    pub fn start_wireless_stack(&self) -> CubeProgrammerResult<()> {
766        self.check_fus_support()?;
767
768        api_types::ReturnCode::<1>::from(unsafe { self.api().startWirelessStack() })
769            .check(crate::error::Action::StartWirelessStack)
770    }
771
772    /// Write [`crate::api_types::CoreRegister`]
773    pub fn write_core_register(
774        &self,
775        register: crate::api_types::CoreRegister,
776        value: u32,
777    ) -> CubeProgrammerResult<()> {
778        self.check_connection()?;
779
780        api_types::ReturnCode::<0>::from(unsafe {
781            self.api().writeCortexRegistres(register.into(), value)
782        })
783        .check(crate::error::Action::WriteCoreRegister)
784    }
785
786    /// Read [`crate::api_types::CoreRegister`]
787    pub fn read_core_register(
788        &self,
789        register: crate::api_types::CoreRegister,
790    ) -> CubeProgrammerResult<u32> {
791        self.check_connection()?;
792
793        let mut value = 0;
794
795        api_types::ReturnCode::<0>::from(unsafe {
796            self.api().readCortexReg(register.into(), &mut value)
797        })
798        .check(crate::error::Action::ReadCoreRegister)?;
799
800        Ok(value)
801    }
802}
803
804impl ConnectedFusProgrammer<'_> {
805    pub fn fus_info(&self) -> &crate::fus::Information {
806        &self.fus_info
807    }
808
809    pub fn delete_wireless_stack(&self) -> CubeProgrammerResult<()> {
810        api_types::ReturnCode::<1>::from(unsafe { self.programmer.api().firmwareDelete() })
811            .check(crate::error::Action::DeleteWirelessStack)
812    }
813
814    pub fn upgrade_wireless_stack(
815        &self,
816        file_path: impl AsRef<std::path::Path>,
817        start_address: u32,
818        first_install: bool,
819        verify: bool,
820        start_stack_after_update: bool,
821    ) -> CubeProgrammerResult<()> {
822        self.programmer.check_connection()?;
823
824        api_types::ReturnCode::<1>::from(unsafe {
825            self.programmer.api().firmwareUpgrade(
826                utility::path_to_widestring(file_path)?.as_ptr(),
827                start_address,
828                if first_install { 1 } else { 0 },
829                if verify { 1 } else { 0 },
830                if start_stack_after_update { 1 } else { 0 },
831            )
832        })
833        .check(crate::error::Action::UpgradeWirelessStack)
834    }
835
836    pub fn start_wireless_stack(&self) -> CubeProgrammerResult<()> {
837        self.programmer.start_wireless_stack()
838    }
839
840    pub fn disconnect(self) {
841        self.programmer.disconnect()
842    }
843}