use crate::{
api_log, api_types, display,
error::{CubeProgrammerError, CubeProgrammerResult},
utility,
};
use bon::bon;
use derive_more::Into;
use log::{debug, error};
use std::{
cell::RefCell,
collections::HashMap,
sync::{Arc, Mutex},
};
use stm32cubeprogrammer_sys::libloading;
use stm32cubeprogrammer_sys::SRAM_BASE_ADDRESS;
macro_rules! verify_api_struct {
($api:expr, $($field:ident),*) => {{
$(
match &$api.$field {
Ok(_) => (), Err(err) => return Err(CubeProgrammerError::MissingDllSymbol{message: format!(
"Missing symbol '{}': {}", stringify!($field), err
)}),
}
)*
Ok(())
}};
}
type ProbeRegistry = HashMap<crate::probe::Serial, Option<crate::probe::Probe>>;
pub struct CubeProgrammer {
api: stm32cubeprogrammer_sys::CubeProgrammer_API,
probe_registry: RefCell<ProbeRegistry>,
}
#[derive(Debug)]
pub struct ConnectedProgrammer<'a> {
programmer: &'a CubeProgrammer,
probe: crate::probe::Probe,
general_information: api_types::GeneralInformation,
}
#[derive(Debug)]
pub struct ConnectedFusProgrammer<'a> {
programmer: ConnectedProgrammer<'a>,
fus_info: crate::fus::Information,
}
#[bon]
impl CubeProgrammer {
#[builder]
pub fn new(
cube_programmer_dir: &impl AsRef<std::path::Path>,
log_verbosity: Option<api_log::Verbosity>,
display_callback: Option<Arc<Mutex<dyn crate::DisplayCallback>>>,
) -> Result<Self, CubeProgrammerError> {
use stm32cubeprogrammer_sys::{PATH_API_LIBRARY_RELATIVE, PATH_LOADER_DIR_RELATIVE};
let api_path = cube_programmer_dir
.as_ref()
.join(PATH_API_LIBRARY_RELATIVE)
.canonicalize()
.map_err(CubeProgrammerError::FileIo)?;
let loader_path = cube_programmer_dir
.as_ref()
.join(PATH_LOADER_DIR_RELATIVE)
.canonicalize()
.map_err(CubeProgrammerError::FileIo)?;
debug!("API path: {:?}", api_path);
debug!("Loader path: {:?}", loader_path);
let library = Self::load_library(&api_path).map_err(CubeProgrammerError::LibLoading)?;
let api = unsafe {
stm32cubeprogrammer_sys::CubeProgrammer_API::from_library(library)
.map_err(CubeProgrammerError::LibLoading)?
};
verify_api_struct!(
api,
setVerbosityLevel,
setDisplayCallbacks,
setLoadersPath,
getStLinkList,
deleteInterfaceList,
connectStLink,
getDeviceGeneralInf,
disconnect,
startFus,
reset,
downloadFile,
massErase,
saveMemoryToFile,
sendOptionBytesCmd,
readUnprotect,
checkDeviceConnection,
readMemory,
freeLibraryMemory,
startWirelessStack,
writeCortexRegistres,
readCortexReg,
firmwareDelete,
firmwareUpgrade
)?;
if let Some(display_callback) = display_callback {
debug!("Set display callback handler");
display::set_display_callback_handler(display_callback);
}
unsafe {
{
let verbosity = log_verbosity.unwrap_or({
debug!("Use default verbosity level");
api_log::Verbosity::Level3
});
debug!("Set verbosity level: {}", verbosity);
api.setVerbosityLevel(verbosity.into());
}
let display_callbacks = stm32cubeprogrammer_sys::displayCallBacks {
initProgressBar: Some(api_log::display_callback_init_progressbar),
logMessage: Some(api_log::display_callback_log_message),
loadBar: Some(api_log::display_callback_load_bar),
};
api.setDisplayCallbacks(display_callbacks);
api.setLoadersPath(utility::path_to_cstring(loader_path)?.as_ptr());
}
Ok(Self {
api,
probe_registry: RefCell::new(HashMap::new()),
})
}
fn scan_for_probes(&self) -> CubeProgrammerResult<()> {
let mut debug_parameters =
std::ptr::null_mut::<stm32cubeprogrammer_sys::debugConnectParameters>();
let return_value = unsafe { self.api.getStLinkList(&mut debug_parameters, 0) };
if return_value < 0 || debug_parameters.is_null() {
return Err(CubeProgrammerError::ActionOutputUnexpected {
action: crate::error::Action::ListConnectedProbes,
unexpected_output: crate::error::UnexpectedOutput::Null,
});
}
let slice = unsafe {
std::slice::from_raw_parts(
debug_parameters as *mut crate::probe::Probe,
return_value as _,
)
};
let mut connected_probes = self.probe_registry.borrow_mut();
connected_probes.retain(|_, value| value.is_none());
for probe in slice {
connected_probes
.entry(probe.serial_number().to_string().into())
.or_insert_with(|| Some(probe.clone()));
}
unsafe {
self.api.deleteInterfaceList();
}
Ok(())
}
pub fn list_available_probes(&self) -> CubeProgrammerResult<Vec<crate::probe::Serial>> {
self.scan_for_probes()?;
let connected_probes = self.probe_registry.borrow();
Ok(connected_probes
.values()
.filter_map(|probe| {
probe
.as_ref()
.map(|probe| probe.serial_number().to_string().into())
})
.collect())
}
fn insert_probe(&self, probe: &crate::probe::Probe) {
let mut connected_probes = self.probe_registry.borrow_mut();
connected_probes.insert(probe.serial_number().to_owned().into(), Some(probe.clone()));
}
pub fn connect_to_target(
&self,
probe_serial_number: &crate::probe::Serial,
protocol: &crate::probe::Protocol,
connection_parameters: &crate::probe::ConnectionParameters,
) -> CubeProgrammerResult<ConnectedProgrammer> {
let mut connected_probes = self.probe_registry.borrow_mut();
if let Some(probe) = connected_probes.get_mut(probe_serial_number) {
if let Some(inner) = probe.take() {
match api_types::ReturnCode::<0>::from(unsafe {
self.api.connectStLink(*crate::probe::Probe::new(
&inner,
protocol,
connection_parameters,
))
})
.check(crate::error::Action::Connect)
{
Ok(_) => {
let general_information = unsafe { self.api.getDeviceGeneralInf() };
if general_information.is_null() {
*probe = Some(inner);
unsafe { self.api.disconnect() };
return Err(CubeProgrammerError::ActionOutputUnexpected {
action: crate::error::Action::ReadTargetInfo,
unexpected_output: crate::error::UnexpectedOutput::Null,
});
}
let general_information =
api_types::GeneralInformation::from(unsafe { *general_information });
Ok(ConnectedProgrammer {
programmer: self,
probe: inner,
general_information,
})
}
Err(e) => {
error!(
"Cannot connect to target via probe with serial number: {}",
probe_serial_number
);
*probe = Some(inner);
Err(e)
}
}
} else {
Err(CubeProgrammerError::Parameter {
action: crate::error::Action::Connect,
message: format!(
"Probe with serial number {} already in use",
probe_serial_number
),
})
}
} else {
Err(CubeProgrammerError::Parameter {
action: crate::error::Action::Connect,
message: format!("Probe with serial number {} not found", probe_serial_number),
})
}
}
pub fn connect_to_target_fus(
&self,
probe_serial_number: &crate::probe::Serial,
protocol: &crate::probe::Protocol,
) -> CubeProgrammerResult<ConnectedFusProgrammer> {
let connected = self.connect_to_target(
probe_serial_number,
protocol,
&crate::probe::ConnectionParameters {
frequency: crate::probe::Frequency::Highest,
reset_mode: crate::probe::ResetMode::Hardware,
connection_mode: crate::probe::ConnectionMode::Normal,
},
)?;
connected.check_fus_support()?;
api_types::ReturnCode::<1>::from(unsafe { connected.api().startFus() })
.check(crate::error::Action::StartFus)?;
connected.disconnect();
let connected = self.connect_to_target(
probe_serial_number,
protocol,
&crate::probe::ConnectionParameters {
frequency: crate::probe::Frequency::Highest,
reset_mode: crate::probe::ResetMode::Hardware,
connection_mode: crate::probe::ConnectionMode::HotPlug,
},
)?;
let fus_info = connected.read_fus_info()?;
Ok(ConnectedFusProgrammer {
programmer: connected,
fus_info,
})
}
fn load_library(
api_library_path: impl AsRef<std::ffi::OsStr>,
) -> Result<libloading::Library, libloading::Error> {
#[cfg(windows)]
unsafe fn load_inner(
path: impl AsRef<std::ffi::OsStr>,
) -> Result<libloading::Library, libloading::Error> {
let library: libloading::Library = unsafe {
libloading::os::windows::Library::load_with_flags(
path,
libloading::os::windows::LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR
| libloading::os::windows::LOAD_LIBRARY_SEARCH_SYSTEM32
| libloading::os::windows::LOAD_LIBRARY_SEARCH_DEFAULT_DIRS,
)?
.into()
};
Ok(library)
}
#[cfg(unix)]
unsafe fn load_inner(
path: impl AsRef<std::ffi::OsStr>,
) -> Result<libloading::Library, libloading::Error> {
use stm32cubeprogrammer_sys::libloading;
let library: libloading::Library =
unsafe { libloading::os::unix::Library::new(path)?.into() };
Ok(library)
}
unsafe { load_inner(api_library_path.as_ref()) }
}
}
impl std::fmt::Debug for CubeProgrammer {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("CubeProgrammerApi").finish_non_exhaustive()
}
}
impl Drop for ConnectedProgrammer<'_> {
fn drop(&mut self) {
unsafe {
self.api().disconnect();
}
self.programmer.insert_probe(&self.probe);
}
}
impl ConnectedProgrammer<'_> {
pub fn disconnect(self) {
}
pub fn general_information(&self) -> &api_types::GeneralInformation {
&self.general_information
}
fn api(&self) -> &stm32cubeprogrammer_sys::CubeProgrammer_API {
&self.programmer.api
}
fn check_fus_support(&self) -> CubeProgrammerResult<()> {
if !self.general_information.fus_support {
return Err(CubeProgrammerError::ActionNotSupported {
action: crate::error::Action::StartFus,
message: format!(
"Connection target {} does not support FUS",
self.general_information.name
),
});
}
Ok(())
}
fn read_fus_info(&self) -> CubeProgrammerResult<crate::fus::Information> {
fn u32_to_version(version: u32) -> crate::fus::Version {
const INFO_VERSION_MAJOR_OFFSET: u32 = 24;
const INFO_VERSION_MAJOR_MASK: u32 = 0xff000000;
const INFO_VERSION_MINOR_OFFSET: u32 = 16;
const INFO_VERSION_MINOR_MASK: u32 = 0x00ff0000;
const INFO_VERSION_SUB_OFFSET: u32 = 8;
const INFO_VERSION_SUB_MASK: u32 = 0x0000ff00;
const INFO_VERSION_TYPE_OFFSET: u32 = 0;
const INFO_VERSION_TYPE_MASK: u32 = 0x00000000f;
crate::fus::Version {
major: ((version & INFO_VERSION_MAJOR_MASK) >> INFO_VERSION_MAJOR_OFFSET) as u8,
minor: ((version & INFO_VERSION_MINOR_MASK) >> INFO_VERSION_MINOR_OFFSET) as u8,
sub: ((version & INFO_VERSION_SUB_MASK) >> INFO_VERSION_SUB_OFFSET) as u8,
r#type: Some(
((version & INFO_VERSION_TYPE_MASK) >> INFO_VERSION_TYPE_OFFSET) as u8,
),
}
}
const DEVICE_INFO_TABLE_STATE_OFFSET: u32 = 0;
const FUS_VERSION_OFFSET: u32 = 12;
const WIRELESS_STACK_VERSION_OFFSET: u32 = 20;
const UID64_OFFSET: u32 = 40;
const DEVICE_ID_OFFSET: u32 = 48;
const FUS_DEVICE_INFO_TABLE_VALIDITY_KEYWORD: u32 = 0xA94656B9;
const SRAM2A_BASE_ADDRESS: u32 = SRAM_BASE_ADDRESS + 0x00030000;
let info_table_address = self.read_memory::<u32>(SRAM2A_BASE_ADDRESS, 1)?[0];
if info_table_address == 0 {
return Err(CubeProgrammerError::ActionOutputUnexpected {
action: crate::error::Action::ReadFusInfo,
unexpected_output: crate::error::UnexpectedOutput::Null,
});
}
let device_info_table_state =
self.read_memory::<u32>(info_table_address + DEVICE_INFO_TABLE_STATE_OFFSET, 1)?[0];
let fus_version = self.read_memory::<u32>(info_table_address + FUS_VERSION_OFFSET, 1)?[0];
let wireless_stack_version =
self.read_memory::<u32>(info_table_address + WIRELESS_STACK_VERSION_OFFSET, 1)?[0];
let uid64 = self.read_memory::<u64>(info_table_address + UID64_OFFSET, 1)?[0];
let device_id = self.read_memory::<u16>(info_table_address + DEVICE_ID_OFFSET, 1)?[0];
if device_info_table_state != FUS_DEVICE_INFO_TABLE_VALIDITY_KEYWORD {
error!("Read FUS info table is not valid. Return default FUS info");
return Err(CubeProgrammerError::ActionOutputUnexpected {
action: crate::error::Action::ReadFusInfo,
unexpected_output: crate::error::UnexpectedOutput::Null,
});
}
Ok(crate::fus::Information {
fus_version: u32_to_version(fus_version),
wireless_stack_version: u32_to_version(wireless_stack_version),
device_id,
uid64,
})
}
pub fn reset_target(&self, reset_mode: crate::probe::ResetMode) -> CubeProgrammerResult<()> {
self.check_connection()?;
api_types::ReturnCode::<0>::from(unsafe { self.api().reset(reset_mode.into()) })
.check(crate::error::Action::Reset)
}
pub fn download_hex_file(
&self,
file_path: impl AsRef<std::path::Path>,
skip_erase: bool,
verify: bool,
) -> CubeProgrammerResult<()> {
#[cfg(feature = "ihex")]
{
let file_content = std::fs::read(&file_path).map_err(CubeProgrammerError::FileIo)?;
let file_content =
std::str::from_utf8(&file_content).map_err(|_| CubeProgrammerError::Parameter {
action: crate::error::Action::DownloadFile,
message: "Invalid intelhex file".to_string(),
})?;
let reader = ihex::Reader::new_with_options(
file_content,
ihex::ReaderOptions {
stop_after_first_error: true,
stop_after_eof: true,
},
);
for record in reader {
match record {
Ok(_) => {}
Err(e) => {
return Err(CubeProgrammerError::Parameter {
action: crate::error::Action::DownloadFile,
message: format!("Invalid intelhex file: {}", e),
});
}
}
}
}
self.check_connection()?;
let file_path = utility::path_to_widestring(file_path);
api_types::ReturnCode::<0>::from(unsafe {
self.api().downloadFile(
file_path?.as_ptr(),
0,
if skip_erase { 1 } else { 0 },
if verify { 1 } else { 0 },
std::ptr::null(),
)
})
.check(crate::error::Action::DownloadFile)
}
pub fn download_bin_file(
&self,
file_path: impl AsRef<std::path::Path>,
start_address: u32,
skip_erase: bool,
verify: bool,
) -> CubeProgrammerResult<()> {
self.check_connection()?;
let file_path = utility::path_to_widestring(file_path);
api_types::ReturnCode::<0>::from(unsafe {
self.api().downloadFile(
file_path?.as_ptr(),
start_address,
if skip_erase { 1 } else { 0 },
if verify { 1 } else { 0 },
std::ptr::null(),
)
})
.check(crate::error::Action::DownloadFile)
}
pub fn mass_erase(&self) -> CubeProgrammerResult<()> {
self.check_connection()?;
api_types::ReturnCode::<0>::from(unsafe { self.api().massErase(std::ptr::null_mut()) })
.check(crate::error::Action::MassErase)
}
pub fn save_memory(
&self,
file_path: impl AsRef<std::path::Path>,
start_address: u32,
size_bytes: u32,
) -> CubeProgrammerResult<()> {
self.check_connection()?;
api_types::ReturnCode::<0>::from(unsafe {
self.api().saveMemoryToFile(
i32::try_from(start_address).map_err(|x| CubeProgrammerError::Parameter {
action: crate::error::Action::SaveMemory,
message: format!("Start address exceeds max value: {}", x),
})?,
i32::try_from(size_bytes).map_err(|x| CubeProgrammerError::Parameter {
action: crate::error::Action::SaveMemory,
message: format!("Size exceeds max value: {}", x),
})?,
utility::path_to_widestring(file_path)?.as_ptr(),
)
})
.check(crate::error::Action::SaveMemory)
}
pub fn enable_read_out_protection(&self) -> CubeProgrammerResult<()> {
const COMMAND_ENABLE_ROP_LEVEL_1: &str = "-ob rdp=0xbb";
self.check_connection()?;
api_types::ReturnCode::<0>::from(unsafe {
self.api().sendOptionBytesCmd(
utility::string_to_cstring(COMMAND_ENABLE_ROP_LEVEL_1)?.as_ptr()
as *mut std::ffi::c_char,
)
})
.check(crate::error::Action::EnableReadOutProtection)
}
pub fn disable_read_out_protection(&self) -> CubeProgrammerResult<()> {
self.check_connection()?;
api_types::ReturnCode::<0>::from(unsafe { self.api().readUnprotect() })
.check(crate::error::Action::DisableReadOutProtection)?;
Ok(())
}
fn check_connection(&self) -> CubeProgrammerResult<()> {
api_types::ReturnCode::<1>::from(unsafe { self.api().checkDeviceConnection() })
.check(crate::error::Action::CheckConnection)
}
pub fn read_memory<T: bytemuck::Pod + bytemuck::Zeroable>(
&self,
address: u32,
count: usize,
) -> CubeProgrammerResult<Vec<T>> {
let size = u32::try_from(std::mem::size_of::<T>() * count).map_err(|x| {
CubeProgrammerError::Parameter {
action: crate::error::Action::ReadMemory,
message: format!("Size exceeds max value: {}", x),
}
})?;
let mut data = std::ptr::null_mut();
api_types::ReturnCode::<0>::from(unsafe {
self.api().readMemory(address, &mut data, size)
})
.check(crate::error::Action::ReadMemory)?;
if data.is_null() {
return Err(CubeProgrammerError::ActionOutputUnexpected {
action: crate::error::Action::ReadMemory,
unexpected_output: crate::error::UnexpectedOutput::Null,
});
}
let pod_data: &[T] =
bytemuck::try_cast_slice(unsafe { std::slice::from_raw_parts(data, size as _) })
.map_err(|_| CubeProgrammerError::ActionOutputUnexpected {
action: crate::error::Action::ReadMemory,
unexpected_output: crate::error::UnexpectedOutput::SliceConversion,
})?;
let pod_data = pod_data.to_vec();
unsafe {
self.api().freeLibraryMemory(data as *mut std::ffi::c_void);
}
if pod_data.len() != count {
return Err(CubeProgrammerError::ActionOutputUnexpected {
action: crate::error::Action::ReadMemory,
unexpected_output: crate::error::UnexpectedOutput::SliceLength,
});
}
Ok(pod_data)
}
pub fn write_memory<T: bytemuck::Pod + std::fmt::Debug>(
&self,
address: u32,
data: &[T],
) -> CubeProgrammerResult<()> {
let size = u32::try_from(std::mem::size_of_val(data)).map_err(|x| {
CubeProgrammerError::Parameter {
action: crate::error::Action::WriteMemory,
message: format!("Size exceeds max value: {}", x),
}
})?;
let mut bytes = data
.iter()
.flat_map(|x| bytemuck::bytes_of(x).to_vec())
.collect::<Vec<_>>();
api_types::ReturnCode::<0>::from(unsafe {
self.api()
.writeMemory(address, bytes.as_mut_ptr() as *mut i8, size)
})
.check(crate::error::Action::WriteMemory)
}
pub fn start_wireless_stack(&self) -> CubeProgrammerResult<()> {
self.check_fus_support()?;
api_types::ReturnCode::<1>::from(unsafe { self.api().startWirelessStack() })
.check(crate::error::Action::StartWirelessStack)
}
pub fn write_core_register(
&self,
register: crate::api_types::CoreRegister,
value: u32,
) -> CubeProgrammerResult<()> {
self.check_connection()?;
api_types::ReturnCode::<0>::from(unsafe {
self.api().writeCortexRegistres(register.into(), value)
})
.check(crate::error::Action::WriteCoreRegister)
}
pub fn read_core_register(
&self,
register: crate::api_types::CoreRegister,
) -> CubeProgrammerResult<u32> {
self.check_connection()?;
let mut value = 0;
api_types::ReturnCode::<0>::from(unsafe {
self.api().readCortexReg(register.into(), &mut value)
})
.check(crate::error::Action::ReadCoreRegister)?;
Ok(value)
}
}
impl ConnectedFusProgrammer<'_> {
pub fn fus_info(&self) -> &crate::fus::Information {
&self.fus_info
}
pub fn delete_wireless_stack(&self) -> CubeProgrammerResult<()> {
api_types::ReturnCode::<1>::from(unsafe { self.programmer.api().firmwareDelete() })
.check(crate::error::Action::DeleteWirelessStack)
}
pub fn upgrade_wireless_stack(
&self,
file_path: impl AsRef<std::path::Path>,
start_address: u32,
first_install: bool,
verify: bool,
start_stack_after_update: bool,
) -> CubeProgrammerResult<()> {
self.programmer.check_connection()?;
api_types::ReturnCode::<1>::from(unsafe {
self.programmer.api().firmwareUpgrade(
utility::path_to_widestring(file_path)?.as_ptr(),
start_address,
if first_install { 1 } else { 0 },
if verify { 1 } else { 0 },
if start_stack_after_update { 1 } else { 0 },
)
})
.check(crate::error::Action::UpgradeWirelessStack)
}
pub fn start_wireless_stack(&self) -> CubeProgrammerResult<()> {
self.programmer.start_wireless_stack()
}
pub fn disconnect(self) {
self.programmer.disconnect()
}
}