From 6d0302ce4623994352fa449cb014081ef55b9a36 Mon Sep 17 00:00:00 2001 From: addrian-77 Date: Mon, 3 Nov 2025 16:09:18 +0200 Subject: [PATCH 1/8] feat:io rework Signed-off-by: addrian-77 --- tockloader-cli/src/main.rs | 30 +-- tockloader-lib/src/board_settings.rs | 2 + tockloader-lib/src/command_impl/erase_apps.rs | 12 ++ .../src/command_impl/generalized.rs | 50 ++--- tockloader-lib/src/command_impl/info.rs | 15 ++ tockloader-lib/src/command_impl/install.rs | 13 ++ tockloader-lib/src/command_impl/list.rs | 13 ++ tockloader-lib/src/command_impl/mod.rs | 4 + .../src/command_impl/probers/erase_apps.rs | 32 --- .../src/command_impl/probers/info.rs | 31 --- .../src/command_impl/probers/install.rs | 161 --------------- tockloader-lib/src/command_impl/probers/io.rs | 66 ++++++ .../src/command_impl/probers/list.rs | 24 --- .../src/command_impl/probers/mod.rs | 5 +- .../src/command_impl/serial/erase_apps.rs | 25 --- .../src/command_impl/serial/info.rs | 31 --- .../src/command_impl/serial/install.rs | 188 ------------------ tockloader-lib/src/command_impl/serial/io.rs | 89 +++++++++ .../src/command_impl/serial/list.rs | 25 --- tockloader-lib/src/command_impl/serial/mod.rs | 5 +- tockloader-lib/src/connection.rs | 29 ++- tockloader-lib/src/known_boards.rs | 2 + tockloader-lib/src/lib.rs | 34 ++-- 23 files changed, 296 insertions(+), 590 deletions(-) create mode 100644 tockloader-lib/src/command_impl/erase_apps.rs create mode 100644 tockloader-lib/src/command_impl/info.rs create mode 100644 tockloader-lib/src/command_impl/install.rs create mode 100644 tockloader-lib/src/command_impl/list.rs delete mode 100644 tockloader-lib/src/command_impl/probers/erase_apps.rs delete mode 100644 tockloader-lib/src/command_impl/probers/info.rs delete mode 100644 tockloader-lib/src/command_impl/probers/install.rs create mode 100644 tockloader-lib/src/command_impl/probers/io.rs delete mode 100644 tockloader-lib/src/command_impl/probers/list.rs delete mode 100644 tockloader-lib/src/command_impl/serial/erase_apps.rs delete mode 100644 tockloader-lib/src/command_impl/serial/info.rs delete mode 100644 tockloader-lib/src/command_impl/serial/install.rs create mode 100644 tockloader-lib/src/command_impl/serial/io.rs delete mode 100644 tockloader-lib/src/command_impl/serial/list.rs diff --git a/tockloader-cli/src/main.rs b/tockloader-cli/src/main.rs index 5d5af442..eaee4323 100644 --- a/tockloader-cli/src/main.rs +++ b/tockloader-cli/src/main.rs @@ -111,8 +111,12 @@ async fn open_connection(user_options: &ArgMatches) -> Result Result Result<()> { cli::validate(&mut cmd, sub_matches); let mut conn = open_connection(sub_matches).await?; - let settings = get_board_settings(sub_matches); - let app_details = conn.list(&settings).await.context("Failed to list apps.")?; + let app_details = conn.list().await.context("Failed to list apps.")?; display::print_list(&app_details).await; } Some(("info", sub_matches)) => { cli::validate(&mut cmd, sub_matches); let mut conn = open_connection(sub_matches).await?; - let settings = get_board_settings(sub_matches); let mut attributes = conn - .info(&settings) + .info() .await .context("Failed to get data from the board.")?; @@ -223,20 +229,16 @@ async fn main() -> Result<()> { .context("Failed to use provided tab file.")?; let mut conn = open_connection(sub_matches).await?; - let settings = get_board_settings(sub_matches); - conn.install_app(&settings, tab_file) + conn.install_app(tab_file) .await .context("Failed to install app.")?; } Some(("erase-apps", sub_matches)) => { cli::validate(&mut cmd, sub_matches); let mut conn = open_connection(sub_matches).await?; - let settings = get_board_settings(sub_matches); - conn.erase_apps(&settings) - .await - .context("Failed to erase apps.")?; + conn.erase_apps().await.context("Failed to erase apps.")?; } _ => { println!("Could not run the provided subcommand."); diff --git a/tockloader-lib/src/board_settings.rs b/tockloader-lib/src/board_settings.rs index d7f67ab8..f9bfec5b 100644 --- a/tockloader-lib/src/board_settings.rs +++ b/tockloader-lib/src/board_settings.rs @@ -1,6 +1,7 @@ pub struct BoardSettings { pub arch: Option, pub start_address: u64, + pub page_size: u64, } // TODO(george-cosma): Does a default implementation make sense for this? Is a @@ -10,6 +11,7 @@ impl Default for BoardSettings { Self { arch: None, start_address: 0x30000, + page_size: 512, } } } diff --git a/tockloader-lib/src/command_impl/erase_apps.rs b/tockloader-lib/src/command_impl/erase_apps.rs new file mode 100644 index 00000000..796286c8 --- /dev/null +++ b/tockloader-lib/src/command_impl/erase_apps.rs @@ -0,0 +1,12 @@ +use async_trait::async_trait; + +use crate::connection::{Connection, TockloaderConnection}; +use crate::errors::TockloaderError; +use crate::{CommandEraseApps, IO}; + +#[async_trait] +impl CommandEraseApps for TockloaderConnection { + async fn erase_apps(&mut self) -> Result<(), TockloaderError> { + self.write(self.get_settings().start_address, &[0u8]).await + } +} diff --git a/tockloader-lib/src/command_impl/generalized.rs b/tockloader-lib/src/command_impl/generalized.rs index 7fe41d85..f0d14601 100644 --- a/tockloader-lib/src/command_impl/generalized.rs +++ b/tockloader-lib/src/command_impl/generalized.rs @@ -1,59 +1,41 @@ use async_trait::async_trait; use crate::attributes::app_attributes::AppAttributes; -use crate::attributes::general_attributes::GeneralAttributes; -use crate::board_settings::BoardSettings; +use crate::attributes::system_attributes::SystemAttributes; use crate::connection::TockloaderConnection; use crate::errors::TockloaderError; -use crate::tabs::tab::Tab; -use crate::{CommandEraseApps, CommandInfo, CommandInstall, CommandList}; +use crate::{IOCommands, IO}; #[async_trait] -impl CommandList for TockloaderConnection { - async fn list( - &mut self, - settings: &BoardSettings, - ) -> Result, TockloaderError> { +impl IO for TockloaderConnection { + async fn read(&mut self, address: u64, size: usize) -> Result, TockloaderError> { match self { - TockloaderConnection::ProbeRS(conn) => conn.list(settings).await, - TockloaderConnection::Serial(conn) => conn.list(settings).await, + TockloaderConnection::ProbeRS(conn) => conn.read(address, size).await, + TockloaderConnection::Serial(conn) => conn.read(address, size).await, } } -} -#[async_trait] -impl CommandInfo for TockloaderConnection { - async fn info( - &mut self, - settings: &BoardSettings, - ) -> Result { + async fn write(&mut self, address: u64, pkt: &[u8]) -> Result<(), TockloaderError> { match self { - TockloaderConnection::ProbeRS(conn) => conn.info(settings).await, - TockloaderConnection::Serial(conn) => conn.info(settings).await, + TockloaderConnection::ProbeRS(conn) => conn.write(address, pkt).await, + TockloaderConnection::Serial(conn) => conn.write(address, pkt).await, } } } #[async_trait] -impl CommandInstall for TockloaderConnection { - async fn install_app( - &mut self, - settings: &BoardSettings, - tab_file: Tab, - ) -> Result<(), TockloaderError> { +impl IOCommands for TockloaderConnection { + async fn read_installed_apps(&mut self) -> Result, TockloaderError> { match self { - TockloaderConnection::ProbeRS(conn) => conn.install_app(settings, tab_file).await, - TockloaderConnection::Serial(conn) => conn.install_app(settings, tab_file).await, + TockloaderConnection::ProbeRS(conn) => conn.read_installed_apps().await, + TockloaderConnection::Serial(conn) => conn.read_installed_apps().await, } } -} -#[async_trait] -impl CommandEraseApps for TockloaderConnection { - async fn erase_apps(&mut self, settings: &BoardSettings) -> Result<(), TockloaderError> { + async fn read_system_attributes(&mut self) -> Result { match self { - TockloaderConnection::ProbeRS(conn) => conn.erase_apps(settings).await, - TockloaderConnection::Serial(conn) => conn.erase_apps(settings).await, + TockloaderConnection::ProbeRS(conn) => conn.read_system_attributes().await, + TockloaderConnection::Serial(conn) => conn.read_system_attributes().await, } } } diff --git a/tockloader-lib/src/command_impl/info.rs b/tockloader-lib/src/command_impl/info.rs new file mode 100644 index 00000000..c87afdb9 --- /dev/null +++ b/tockloader-lib/src/command_impl/info.rs @@ -0,0 +1,15 @@ +use async_trait::async_trait; + +use crate::attributes::general_attributes::GeneralAttributes; +use crate::connection::TockloaderConnection; +use crate::errors::TockloaderError; +use crate::{CommandInfo, IOCommands}; + +#[async_trait] +impl CommandInfo for TockloaderConnection { + async fn info(&mut self) -> Result { + let installed_apps = self.read_installed_apps().await.unwrap(); + let system_atributes = self.read_system_attributes().await.unwrap(); + Ok(GeneralAttributes::new(system_atributes, installed_apps)) + } +} diff --git a/tockloader-lib/src/command_impl/install.rs b/tockloader-lib/src/command_impl/install.rs new file mode 100644 index 00000000..cae2b7ac --- /dev/null +++ b/tockloader-lib/src/command_impl/install.rs @@ -0,0 +1,13 @@ +use async_trait::async_trait; + +use crate::connection::TockloaderConnection; +use crate::errors::TockloaderError; +use crate::tabs::tab::Tab; +use crate::CommandInstall; + +#[async_trait] +impl CommandInstall for TockloaderConnection { + async fn install_app(&mut self, _tab: Tab) -> Result<(), TockloaderError> { + todo!() + } +} diff --git a/tockloader-lib/src/command_impl/list.rs b/tockloader-lib/src/command_impl/list.rs new file mode 100644 index 00000000..f024285f --- /dev/null +++ b/tockloader-lib/src/command_impl/list.rs @@ -0,0 +1,13 @@ +use async_trait::async_trait; + +use crate::attributes::app_attributes::AppAttributes; +use crate::connection::TockloaderConnection; +use crate::errors::TockloaderError; +use crate::{CommandList, IOCommands}; + +#[async_trait] +impl CommandList for TockloaderConnection { + async fn list(&mut self) -> Result, TockloaderError> { + self.read_installed_apps().await + } +} diff --git a/tockloader-lib/src/command_impl/mod.rs b/tockloader-lib/src/command_impl/mod.rs index 46aa06ea..84d774ea 100644 --- a/tockloader-lib/src/command_impl/mod.rs +++ b/tockloader-lib/src/command_impl/mod.rs @@ -1,3 +1,7 @@ +pub mod erase_apps; pub mod generalized; +pub mod info; +pub mod install; +pub mod list; pub mod probers; pub mod serial; diff --git a/tockloader-lib/src/command_impl/probers/erase_apps.rs b/tockloader-lib/src/command_impl/probers/erase_apps.rs deleted file mode 100644 index 24e90f2f..00000000 --- a/tockloader-lib/src/command_impl/probers/erase_apps.rs +++ /dev/null @@ -1,32 +0,0 @@ -use async_trait::async_trait; -use probe_rs::flashing::DownloadOptions; - -use crate::board_settings::BoardSettings; -use crate::connection::{Connection, ProbeRSConnection}; -use crate::errors::{InternalError, TockloaderError}; -use crate::CommandEraseApps; - -#[async_trait] -impl CommandEraseApps for ProbeRSConnection { - async fn erase_apps(&mut self, settings: &BoardSettings) -> Result<(), TockloaderError> { - if !self.is_open() { - return Err(InternalError::ConnectionNotOpen.into()); - } - let session = self.session.as_mut().expect("Board must be open"); - - let mut loader = session.target().flash_loader(); - - let address = settings.start_address; - // A single 0x0 byte is enough to invalidate the tbf header and make it all programs - // unreadable to tockloader. This does mean app information will still exist on the board, - // but they will be overwritten when the space is needed. - loader.add_data((address as u32).into(), &[0x0])?; - - let mut options = DownloadOptions::default(); - options.keep_unwritten_bytes = true; - - // Finally, the data can be programmed - loader.commit(session, options)?; - Ok(()) - } -} diff --git a/tockloader-lib/src/command_impl/probers/info.rs b/tockloader-lib/src/command_impl/probers/info.rs deleted file mode 100644 index 113f2360..00000000 --- a/tockloader-lib/src/command_impl/probers/info.rs +++ /dev/null @@ -1,31 +0,0 @@ -use async_trait::async_trait; - -use crate::attributes::app_attributes::AppAttributes; -use crate::attributes::general_attributes::GeneralAttributes; -use crate::attributes::system_attributes::SystemAttributes; -use crate::board_settings::BoardSettings; -use crate::connection::{Connection, ProbeRSConnection}; -use crate::errors::{InternalError, TockloaderError}; -use crate::CommandInfo; - -#[async_trait] -impl CommandInfo for ProbeRSConnection { - async fn info( - &mut self, - settings: &BoardSettings, - ) -> Result { - if !self.is_open() { - return Err(InternalError::ConnectionNotOpen.into()); - } - let session = self.session.as_mut().expect("Board must be open"); - - let mut core = session.core(self.target_info.core)?; - - // TODO(george-cosma): extract these informations without bootloader - let system_attributes = SystemAttributes::read_system_attributes_probe(&mut core)?; - let app_attributes = - AppAttributes::read_apps_data_probe(&mut core, settings.start_address)?; - - Ok(GeneralAttributes::new(system_attributes, app_attributes)) - } -} diff --git a/tockloader-lib/src/command_impl/probers/install.rs b/tockloader-lib/src/command_impl/probers/install.rs deleted file mode 100644 index af84572f..00000000 --- a/tockloader-lib/src/command_impl/probers/install.rs +++ /dev/null @@ -1,161 +0,0 @@ -use async_trait::async_trait; -use probe_rs::flashing::DownloadOptions; -use probe_rs::MemoryInterface; -use tbf_parser::parse::parse_tbf_header_lengths; - -use crate::board_settings::BoardSettings; -use crate::connection::{Connection, ProbeRSConnection}; -use crate::errors::{InternalError, TockloaderError}; -use crate::tabs::tab::Tab; -use crate::CommandInstall; - -#[async_trait] -impl CommandInstall for ProbeRSConnection { - async fn install_app( - &mut self, - settings: &BoardSettings, - tab_file: Tab, - ) -> Result<(), TockloaderError> { - if !self.is_open() { - return Err(InternalError::ConnectionNotOpen.into()); - } - let session = self.session.as_mut().expect("Board must be open"); - - let mut core = session.core(self.target_info.core)?; - - // TODO(george-cosma): extract these informations without bootloader - // TODO(george-cosma): extract board name and kernel version to verify app compatability - - let mut address = settings.start_address; - - // TODO(george-cosma): double-check/rework this - - // Read a block of 200 8-bit words// Loop to check if there are another apps installed - loop { - let mut buff = vec![0u8; 200]; - core.read(address, &mut buff)?; - - let (_ver, _header_len, whole_len) = match parse_tbf_header_lengths( - &buff[0..8] - .try_into() - .expect("Buffer length must be at least 8 bytes long."), - ) { - Ok((ver, header_len, whole_len)) if header_len != 0 => (ver, header_len, whole_len), - _ => break, // No more apps - }; - address += whole_len as u64; - } - - // TODO(george-cosma): extract arch(?) - // TODO(george-cosma): THIS IS NOT A TOCK ERROR, this is an error due to invalid board settings. - let arch = settings - .arch - .as_ref() - .ok_or(InternalError::MisconfiguredBoardSettings( - "architechture".to_owned(), - ))?; - - let mut binary = tab_file.extract_binary(arch)?; - let size = binary.len() as u64; - - // Make sure the app is aligned to a multiple of its size - let multiple = address / size; - - let (new_address, _gap_size) = if multiple * size != address { - let new_address = ((address + size) / size) * size; - let gap_size = new_address - address; - (new_address, gap_size) - } else { - (address, 0) - }; - - // TODO(george-cosma): This point MIGHT mark a good point to split - // this function (for probe-rs). - - // At this point we no longer need to hold the probe-rs connection - // to the core, as the flashing is done without it. - drop(core); - - // Make sure the binary is a multiple of the page size by padding 0xFFs - - // TODO(george-cosma): check if the page-size differs + support - // multiple types of page sizes. Possibly make page size a board - // setting. - let page_size = 512; - let needs_padding = binary.len() % page_size != 0; - - if needs_padding { - let remaining = page_size - (binary.len() % page_size); - dbg!(remaining); - for _i in 0..remaining { - binary.push(0xFF); - } - } - - // Get indices of pages that have valid data to write - let mut valid_pages: Vec = Vec::new(); - for i in 0..(size as usize / page_size) { - for b in binary[(i * page_size)..((i + 1) * page_size)] - .iter() - .copied() - { - if b != 0 { - valid_pages.push(i.try_into().unwrap()); - break; - } - } - } - - // If there are no pages valid, all pages would have been removed, - // so we write them all - if valid_pages.is_empty() { - for i in 0..(size as usize / page_size) { - valid_pages.push(i.try_into().unwrap()); - } - } - - // Include a blank page (if exists) after the end of a valid page. - // There might be a usable 0 on the next page - let mut ending_pages: Vec = Vec::new(); - for &i in &valid_pages { - let mut iter = valid_pages.iter(); - if !iter.any(|&x| x == (i + 1)) && (i + 1) < (size as usize / page_size) as u8 { - ending_pages.push(i + 1); - } - } - - for i in ending_pages { - valid_pages.push(i); - } - - for i in valid_pages { - println!("Writing page number {i}"); - // Create the packet that we send to the bootloader. First four - // bytes are the address of the page - let mut pkt = Vec::new(); - - // Then the bytes that go into the page - for b in binary[(i as usize * page_size)..((i + 1) as usize * page_size)] - .iter() - .copied() - { - pkt.push(b); - } - let mut loader = session.target().flash_loader(); - - loader.add_data( - (new_address as u32 + (i as usize * page_size) as u32).into(), - &pkt, - )?; - - let mut options = DownloadOptions::default(); - options.keep_unwritten_bytes = true; - - // Finally, the data can be programmed - // TODO(george-cosma): Can we move this outside the loop? Commit once? - loader.commit(session, options)?; - } - - Ok(()) - } -} diff --git a/tockloader-lib/src/command_impl/probers/io.rs b/tockloader-lib/src/command_impl/probers/io.rs new file mode 100644 index 00000000..dcc2367f --- /dev/null +++ b/tockloader-lib/src/command_impl/probers/io.rs @@ -0,0 +1,66 @@ +use async_trait::async_trait; +use probe_rs::{flashing::DownloadOptions, MemoryInterface}; + +use crate::{ + attributes::{app_attributes::AppAttributes, system_attributes::SystemAttributes}, + connection::{Connection, ProbeRSConnection}, + errors::{InternalError, TockloaderError}, + IOCommands, IO, +}; + +#[async_trait] +impl IO for ProbeRSConnection { + async fn read(&mut self, address: u64, size: usize) -> Result, TockloaderError> { + if !self.is_open() { + return Err(InternalError::ConnectionNotOpen.into()); + } + let session = self.session.as_mut().expect("Board must be open"); + + let mut core = session.core(self.target_info.core)?; + let mut appdata = vec![0u8; size]; + core.read(address, &mut appdata)?; + Ok(appdata) + } + + async fn write(&mut self, address: u64, pkt: &[u8]) -> Result<(), TockloaderError> { + if !self.is_open() { + return Err(InternalError::ConnectionNotOpen.into()); + } + let session = self.session.as_mut().expect("Board must be open"); + let mut loader = session.target().flash_loader(); + + loader.add_data(address, pkt)?; + + let mut options = DownloadOptions::default(); + options.keep_unwritten_bytes = true; + + loader.commit(session, options)?; + Ok(()) + } +} + +#[async_trait] +impl IOCommands for ProbeRSConnection { + async fn read_installed_apps(&mut self) -> Result, TockloaderError> { + if !self.is_open() { + return Err(InternalError::ConnectionNotOpen.into()); + } + let start_address = self.get_settings().start_address; + let session = self.session.as_mut().expect("Board must be open"); + let mut core = session.core(self.target_info.core)?; + + AppAttributes::read_apps_data_probe(&mut core, start_address) + } + + async fn read_system_attributes(&mut self) -> Result { + if !self.is_open() { + return Err(InternalError::ConnectionNotOpen.into()); + } + let session = self.session.as_mut().expect("Board must be open"); + + let mut core = session.core(self.target_info.core)?; + + let system_attributes = SystemAttributes::read_system_attributes_probe(&mut core)?; + Ok(system_attributes) + } +} diff --git a/tockloader-lib/src/command_impl/probers/list.rs b/tockloader-lib/src/command_impl/probers/list.rs deleted file mode 100644 index 11eab7fb..00000000 --- a/tockloader-lib/src/command_impl/probers/list.rs +++ /dev/null @@ -1,24 +0,0 @@ -use async_trait::async_trait; - -use crate::attributes::app_attributes::AppAttributes; -use crate::board_settings::BoardSettings; -use crate::connection::{Connection, ProbeRSConnection}; -use crate::errors::{InternalError, TockloaderError}; -use crate::CommandList; - -#[async_trait] -impl CommandList for ProbeRSConnection { - async fn list( - &mut self, - settings: &BoardSettings, - ) -> Result, TockloaderError> { - if !self.is_open() { - return Err(InternalError::ConnectionNotOpen.into()); - } - let session = self.session.as_mut().expect("Board must be open"); - - let mut core = session.core(self.target_info.core)?; - - AppAttributes::read_apps_data_probe(&mut core, settings.start_address) - } -} diff --git a/tockloader-lib/src/command_impl/probers/mod.rs b/tockloader-lib/src/command_impl/probers/mod.rs index dda4278b..af514a1e 100644 --- a/tockloader-lib/src/command_impl/probers/mod.rs +++ b/tockloader-lib/src/command_impl/probers/mod.rs @@ -1,4 +1 @@ -pub mod erase_apps; -pub mod info; -pub mod install; -pub mod list; +pub mod io; diff --git a/tockloader-lib/src/command_impl/serial/erase_apps.rs b/tockloader-lib/src/command_impl/serial/erase_apps.rs deleted file mode 100644 index 4e4c87a7..00000000 --- a/tockloader-lib/src/command_impl/serial/erase_apps.rs +++ /dev/null @@ -1,25 +0,0 @@ -use async_trait::async_trait; - -use crate::board_settings::BoardSettings; -use crate::bootloader_serial::{ - issue_command, ping_bootloader_and_wait_for_response, Command, Response, -}; -use crate::connection::{Connection, SerialConnection}; -use crate::errors::{InternalError, TockloaderError}; -use crate::CommandEraseApps; - -#[async_trait] -impl CommandEraseApps for SerialConnection { - async fn erase_apps(&mut self, settings: &BoardSettings) -> Result<(), TockloaderError> { - if !self.is_open() { - return Err(InternalError::ConnectionNotOpen.into()); - } - let stream = self.stream.as_mut().expect("Board must be open"); - - ping_bootloader_and_wait_for_response(stream).await?; - - let pkt = (settings.start_address as u32).to_le_bytes().to_vec(); - let (_, _) = issue_command(stream, Command::ErasePage, pkt, true, 0, Response::OK).await?; - Ok(()) - } -} diff --git a/tockloader-lib/src/command_impl/serial/info.rs b/tockloader-lib/src/command_impl/serial/info.rs deleted file mode 100644 index 57770ccf..00000000 --- a/tockloader-lib/src/command_impl/serial/info.rs +++ /dev/null @@ -1,31 +0,0 @@ -use async_trait::async_trait; - -use crate::attributes::app_attributes::AppAttributes; -use crate::attributes::general_attributes::GeneralAttributes; -use crate::attributes::system_attributes::SystemAttributes; -use crate::board_settings::BoardSettings; -use crate::bootloader_serial::ping_bootloader_and_wait_for_response; -use crate::connection::{Connection, SerialConnection}; -use crate::errors::{InternalError, TockloaderError}; -use crate::CommandInfo; - -#[async_trait] -impl CommandInfo for SerialConnection { - async fn info( - &mut self, - settings: &BoardSettings, - ) -> Result { - if !self.is_open() { - return Err(InternalError::ConnectionNotOpen.into()); - } - let stream = self.stream.as_mut().expect("Board must be open"); - - ping_bootloader_and_wait_for_response(stream).await?; - - let system_attributes = SystemAttributes::read_system_attributes_serial(stream).await?; - let app_attributes = - AppAttributes::read_apps_data_serial(stream, settings.start_address).await?; - - Ok(GeneralAttributes::new(system_attributes, app_attributes)) - } -} diff --git a/tockloader-lib/src/command_impl/serial/install.rs b/tockloader-lib/src/command_impl/serial/install.rs deleted file mode 100644 index 6f84d01c..00000000 --- a/tockloader-lib/src/command_impl/serial/install.rs +++ /dev/null @@ -1,188 +0,0 @@ -use async_trait::async_trait; - -use crate::attributes::system_attributes::SystemAttributes; -use crate::board_settings::BoardSettings; -use crate::bootloader_serial::{issue_command, Command, Response}; -use crate::connection::Connection; -use crate::connection::SerialConnection; -use crate::errors::InternalError; -use crate::errors::TockloaderError; -use crate::tabs::tab::Tab; -use crate::CommandInstall; -use tbf_parser::parse::parse_tbf_header_lengths; - -#[async_trait] -impl CommandInstall for SerialConnection { - async fn install_app( - &mut self, - settings: &BoardSettings, - tab_file: Tab, - ) -> Result<(), TockloaderError> { - if !self.is_open() { - return Err(TockloaderError::Internal(InternalError::ConnectionNotOpen)); - } - - let stream = self.stream.as_mut().expect( - "Expected serial stream to be initialized. This should not happen if setup is correct.", - ); - - let system_attributes = SystemAttributes::read_system_attributes_serial(stream).await?; - - let board = system_attributes - .board - .ok_or("No board name found.".to_owned()) - .map_err(|e| TockloaderError::Internal(InternalError::MisconfiguredBoardSettings(e)))?; - //TODO: handle the case when board is not set - let kernel_version = system_attributes - .kernel_version - .ok_or("No kernel version found.".to_owned()) - .map_err(|e| TockloaderError::Internal(InternalError::MisconfiguredBoardSettings(e)))?; - - if tab_file.is_compatible_with_board(&board) { - log::info!("Specified tab is compatible with board."); - } else { - //TODO: replace with appropriate error - panic!("Specified tab is not compatible with board."); - } - if tab_file.is_compatible_with_kernel_verison(kernel_version as u32) { - log::info!("Specified tab is compatible with your kernel version."); - } else { - log::info!("Specified tab is not compatible with your kernel version."); - } - - let mut address = match system_attributes.appaddr { - Some(addr) => { - log::info!("App start address found in system attributes."); - addr - } - None => { - log::info!( - "No start address found in system attributes. Falling back to board settings." - ); - settings.start_address - } - }; - - loop { - // Read a block of 200 8-bit words - let mut pkt = (address as u32).to_le_bytes().to_vec(); - let length = (200_u16).to_le_bytes().to_vec(); - pkt.extend(length); - - let (_, message) = issue_command( - stream, - Command::ReadRange, - pkt, - true, - 200, - Response::ReadRange, - ) - .await?; - - // TODO: handle error properly - - // let (ver, header_len, whole_len) = parse_tbf_header_lengths( - // &message[0..8] - // .try_into() - // .expect("Buffer length must be at least 8 bytes long."), - // //TODO: select an appropriate error - // ) - // .unwrap(); - - // if header_len == 0 { - // break; // No more apps - // } - - let (_ver, _header_len, whole_len) = match parse_tbf_header_lengths( - &message[0..8] - .try_into() - .expect("Buffer length must be at least 8 bytes long."), - ) { - Ok((ver, header_len, whole_len)) if header_len != 0 => (ver, header_len, whole_len), - _ => break, // No more apps - }; - - address += whole_len as u64; - } - - let arch = system_attributes - .arch - .ok_or("No architecture found.".to_owned()) - .map_err(|e| TockloaderError::Internal(InternalError::MisconfiguredBoardSettings(e)))?; - - let mut binary = tab_file.extract_binary(&arch.clone())?; - - let size = binary.len() as u64; - - let multiple = address / size; - - let (mut new_address, _gap_size) = if multiple * size != address { - let new_address = ((address + size) / size) * size; - let gap_size = new_address - address; - (new_address, gap_size) - } else { - (address, 0) - }; - - // Make sure the binary is a multiple of the page size by padding 0xFFs - // TODO(Micu Ana): check if the page-size differs - let page_size = 512; - - let remaining = page_size - (binary.len() % page_size); - let padding = vec![0xFF; remaining % page_size]; - binary.extend(padding); - - let binary_len = binary.len(); - - // Get indices of pages that have valid data to write - let mut valid_pages: Vec = Vec::new(); - - valid_pages.extend( - (0..(binary_len / page_size)) - .filter(|&i| { - binary[(i * page_size)..((i + 1) * page_size)] - .iter() - .any(|&b| b != 0) - }) - .map(|i| i as u8), - ); - - // If there are no pages valid, all pages would have been removed, so we write them all - // Fallback that ensures old data is cleared and that aren't any partially written apps - if valid_pages.is_empty() { - valid_pages.extend((0..(binary_len / page_size)).map(|i| i as u8)); - } - - // Include a blank page (if exists) after the end of a valid page. There might be a usable 0 on the next page - let existing_pages = valid_pages.clone(); - - let ending_pages = existing_pages.iter().map(|&i| i + 1).filter(|&next| { - next < (binary_len / page_size) as u8 && !existing_pages.contains(&next) - }); - - valid_pages.extend(ending_pages); - - for i in valid_pages { - // Create the packet that we send to the bootloader - // First four bytes are the address of the page - let mut pkt = (new_address as u32 + (i as usize * page_size) as u32) - .to_le_bytes() - .to_vec(); - // Then the bytes that go into the page - pkt.extend(&binary[(i as usize * page_size)..((i + 1) as usize * page_size)]); - - // Write to bootloader - let (_, _) = - issue_command(stream, Command::WritePage, pkt, true, 0, Response::OK).await?; - } - - new_address += binary.len() as u64; - - let pkt = (new_address as u32).to_le_bytes().to_vec(); - - // Empty page marks the end of the apps list - let _ = issue_command(stream, Command::ErasePage, pkt, true, 0, Response::OK).await?; - - Ok(()) - } -} diff --git a/tockloader-lib/src/command_impl/serial/io.rs b/tockloader-lib/src/command_impl/serial/io.rs new file mode 100644 index 00000000..6751559b --- /dev/null +++ b/tockloader-lib/src/command_impl/serial/io.rs @@ -0,0 +1,89 @@ +use async_trait::async_trait; + +use crate::{ + attributes::{app_attributes::AppAttributes, system_attributes::SystemAttributes}, + bootloader_serial::{issue_command, ping_bootloader_and_wait_for_response, Command, Response}, + connection::{Connection, SerialConnection}, + errors::{InternalError, TockloaderError}, + IOCommands, IO, +}; + +#[async_trait] +impl IO for SerialConnection { + async fn read(&mut self, address: u64, size: usize) -> Result, TockloaderError> { + let mut pkt = (address as u32).to_le_bytes().to_vec(); + pkt.append(&mut (size as u16).to_le_bytes().to_vec()); + let stream = self.stream.as_mut().expect("Board must be open"); + + let (_, appdata) = issue_command( + stream, + Command::ReadRange, + pkt, + true, + size, + Response::ReadRange, + ) + .await?; + + if appdata.len() < size { + // Sanity check that we wrote everything. This was previously failing + // due to not reading enough when encountering double ESCAPE_CHAR. + panic!("Internal Error: When reading from a Serial connection, we read less bytes than requested despite previous checks."); + } + Ok(appdata) + } + + async fn write(&mut self, address: u64, pkt: &[u8]) -> Result<(), TockloaderError> { + let page_size = self.get_settings().page_size as usize; + let stream = self.stream.as_mut().expect("Board must be open"); + let mut binary = pkt.to_vec(); + + if !binary.len().is_multiple_of(page_size) { + binary.extend(vec![0u8; page_size - (binary.len() % page_size)]); + } + + for page_number in 0..(binary.len() / page_size) { + let mut pkt = (address as u32 + page_number as u32 * page_size as u32) + .to_le_bytes() + .to_vec(); + pkt.append( + &mut binary[(page_number * page_size)..((page_number + 1) * page_size)].to_vec(), + ); + let _ = issue_command(stream, Command::WritePage, pkt, true, 0, Response::OK).await?; + } + + let pkt = (address as u32 + binary.len() as u32) + .to_le_bytes() + .to_vec(); + + let _ = issue_command(stream, Command::ErasePage, pkt, true, 0, Response::OK).await?; + Ok(()) + } +} + +#[async_trait] +impl IOCommands for SerialConnection { + async fn read_installed_apps(&mut self) -> Result, TockloaderError> { + if !self.is_open() { + return Err(InternalError::ConnectionNotOpen.into()); + } + let start_address = self.get_settings().start_address; + let stream = self.stream.as_mut().expect("Board must be open"); + + ping_bootloader_and_wait_for_response(stream).await?; + + AppAttributes::read_apps_data_serial(stream, start_address).await + } + + async fn read_system_attributes(&mut self) -> Result { + if !self.is_open() { + return Err(InternalError::ConnectionNotOpen.into()); + } + let stream = self.stream.as_mut().expect("Board must be open"); + + ping_bootloader_and_wait_for_response(stream).await?; + + let system_attributes = SystemAttributes::read_system_attributes_serial(stream).await?; + Ok(system_attributes) + } +} diff --git a/tockloader-lib/src/command_impl/serial/list.rs b/tockloader-lib/src/command_impl/serial/list.rs deleted file mode 100644 index 268a9040..00000000 --- a/tockloader-lib/src/command_impl/serial/list.rs +++ /dev/null @@ -1,25 +0,0 @@ -use async_trait::async_trait; - -use crate::attributes::app_attributes::AppAttributes; -use crate::board_settings::BoardSettings; -use crate::bootloader_serial::ping_bootloader_and_wait_for_response; -use crate::connection::{Connection, SerialConnection}; -use crate::errors::{InternalError, TockloaderError}; -use crate::CommandList; - -#[async_trait] -impl CommandList for SerialConnection { - async fn list( - &mut self, - settings: &BoardSettings, - ) -> Result, TockloaderError> { - if !self.is_open() { - return Err(InternalError::ConnectionNotOpen.into()); - } - let stream = self.stream.as_mut().expect("Board must be open"); - - ping_bootloader_and_wait_for_response(stream).await?; - - AppAttributes::read_apps_data_serial(stream, settings.start_address).await - } -} diff --git a/tockloader-lib/src/command_impl/serial/mod.rs b/tockloader-lib/src/command_impl/serial/mod.rs index dda4278b..af514a1e 100644 --- a/tockloader-lib/src/command_impl/serial/mod.rs +++ b/tockloader-lib/src/command_impl/serial/mod.rs @@ -1,4 +1 @@ -pub mod erase_apps; -pub mod info; -pub mod install; -pub mod list; +pub mod io; diff --git a/tockloader-lib/src/connection.rs b/tockloader-lib/src/connection.rs index 7d0c6939..bb3cab38 100644 --- a/tockloader-lib/src/connection.rs +++ b/tockloader-lib/src/connection.rs @@ -6,6 +6,7 @@ use probe_rs::{Permissions, Session}; use tokio::io::AsyncWriteExt; use tokio_serial::{FlowControl, Parity, SerialPort, SerialStream, StopBits}; +use crate::board_settings::BoardSettings; use crate::errors::TockloaderError; use log::info; pub struct ProbeTargetInfo { @@ -51,6 +52,7 @@ pub trait Connection { /// `open` or any other method is undefined behavior. async fn close(&mut self) -> Result<(), TockloaderError>; fn is_open(&self) -> bool; + fn get_settings(&self) -> &BoardSettings; } pub struct ProbeRSConnection { @@ -60,14 +62,20 @@ pub struct ProbeRSConnection { pub(crate) target_info: ProbeTargetInfo, /// Only used for opening a new connection debug_probe: DebugProbeInfo, + settings: BoardSettings, } impl ProbeRSConnection { - pub fn new(debug_probe: DebugProbeInfo, target_info: ProbeTargetInfo) -> Self { + pub fn new( + debug_probe: DebugProbeInfo, + target_info: ProbeTargetInfo, + settings: BoardSettings, + ) -> Self { Self { session: None, target_info, debug_probe, + settings, } } } @@ -93,6 +101,10 @@ impl Connection for ProbeRSConnection { fn is_open(&self) -> bool { self.session.is_some() } + + fn get_settings(&self) -> &BoardSettings { + &self.settings + } } pub struct SerialConnection { @@ -102,14 +114,16 @@ pub struct SerialConnection { pub(crate) target_info: SerialTargetInfo, /// Path to the serial port. This is only used for opening a new connection. port: String, + settings: BoardSettings, } impl SerialConnection { - pub fn new(port: String, target_info: SerialTargetInfo) -> Self { + pub fn new(port: String, target_info: SerialTargetInfo, settings: BoardSettings) -> Self { Self { stream: None, target_info, port, + settings, } } pub fn into_inner_stream(self) -> Option { @@ -150,6 +164,10 @@ impl Connection for SerialConnection { fn is_open(&self) -> bool { self.stream.is_some() } + + fn get_settings(&self) -> &BoardSettings { + &self.settings + } } /// This is an utility enum to make your life easier when you want to abstract @@ -194,4 +212,11 @@ impl Connection for TockloaderConnection { TockloaderConnection::Serial(conn) => conn.is_open(), } } + + fn get_settings(&self) -> &BoardSettings { + match self { + TockloaderConnection::ProbeRS(conn) => conn.get_settings(), + TockloaderConnection::Serial(conn) => conn.get_settings(), + } + } } diff --git a/tockloader-lib/src/known_boards.rs b/tockloader-lib/src/known_boards.rs index 52dc0bac..6a986273 100644 --- a/tockloader-lib/src/known_boards.rs +++ b/tockloader-lib/src/known_boards.rs @@ -25,6 +25,7 @@ impl KnownBoard for NucleoF4 { BoardSettings { arch: Some("cortex-m4".to_string()), start_address: 0x08040000, + page_size: 512, } } } @@ -47,6 +48,7 @@ impl KnownBoard for MicrobitV2 { BoardSettings { arch: Some("cortex-m4".to_string()), start_address: 0x00040000, + page_size: 512, } } } diff --git a/tockloader-lib/src/lib.rs b/tockloader-lib/src/lib.rs index fdb79c5f..fd023eef 100644 --- a/tockloader-lib/src/lib.rs +++ b/tockloader-lib/src/lib.rs @@ -17,7 +17,7 @@ use tokio_serial::SerialPortInfo; use crate::attributes::app_attributes::AppAttributes; use crate::attributes::general_attributes::GeneralAttributes; -use crate::board_settings::BoardSettings; +use crate::attributes::system_attributes::SystemAttributes; use crate::errors::*; use crate::tabs::tab::Tab; @@ -37,30 +37,34 @@ pub fn list_serial_ports() -> Result, TockloaderError> { #[async_trait] pub trait CommandList { - async fn list( - &mut self, - settings: &BoardSettings, - ) -> Result, TockloaderError>; + async fn list(&mut self) -> Result, TockloaderError>; } #[async_trait] pub trait CommandInfo { - async fn info( - &mut self, - settings: &BoardSettings, - ) -> Result; + async fn info(&mut self) -> Result; } #[async_trait] pub trait CommandInstall { - async fn install_app( - &mut self, - settings: &BoardSettings, - tab_file: Tab, - ) -> Result<(), TockloaderError>; + async fn install_app(&mut self, tab_file: Tab) -> Result<(), TockloaderError>; } #[async_trait] pub trait CommandEraseApps { - async fn erase_apps(&mut self, settings: &BoardSettings) -> Result<(), TockloaderError>; + async fn erase_apps(&mut self) -> Result<(), TockloaderError>; +} + +#[async_trait] +pub trait IO: Send { + async fn read(&mut self, address: u64, size: usize) -> Result, TockloaderError>; + + async fn write(&mut self, address: u64, pkt: &[u8]) -> Result<(), TockloaderError>; +} + +#[async_trait] +pub trait IOCommands: Send { + async fn read_installed_apps(&mut self) -> Result, TockloaderError>; + + async fn read_system_attributes(&mut self) -> Result; } From bbccf8ea0b689e155cd0a962542e2c88696da76f Mon Sep 17 00:00:00 2001 From: Adrian Lungu Date: Tue, 9 Dec 2025 12:21:30 +0200 Subject: [PATCH 2/8] refactor: read system attributes Signed-off-by: Adrian Lungu --- .../src/attributes/system_attributes.rs | 167 ++---------------- tockloader-lib/src/command_impl/probers/io.rs | 6 +- tockloader-lib/src/command_impl/serial/io.rs | 2 +- 3 files changed, 13 insertions(+), 162 deletions(-) diff --git a/tockloader-lib/src/attributes/system_attributes.rs b/tockloader-lib/src/attributes/system_attributes.rs index aab02775..fbbe6bfe 100644 --- a/tockloader-lib/src/attributes/system_attributes.rs +++ b/tockloader-lib/src/attributes/system_attributes.rs @@ -3,11 +3,9 @@ // Copyright OXIDOS AUTOMOTIVE 2024. use byteorder::{ByteOrder, LittleEndian}; -use probe_rs::{Core, MemoryInterface}; -use tokio_serial::SerialStream; -use crate::bootloader_serial::{issue_command, Command, Response}; use crate::errors::{AttributeParseError, TockError, TockloaderError}; +use crate::IO; use super::decode::{bytes_to_string, decode_attribute}; @@ -50,29 +48,27 @@ impl SystemAttributes { } } - /// Read system attributes using a probe-rs connection. A bootloader must be + /// Read system attributes using a generalized connection. A bootloader must be /// present on this board for this function to work properly. /// /// # Parameters - /// - `board_core` : Core access, obtained from a - /// [ProbeRSConnection](crate::connection::ProbeRSConnection) + /// - `conn` : Either a SerialConnection or a ProbeRSConnection /// /// # Returns /// - Ok(result): if attributes were read successfully /// - Err(TockloaderError::MisconfiguredBoard): if no start address is found or valid /// - Err(TockloaderError::MisconfiguredBoard): if attributes don't follow the UTF-8 format - /// - Err(TockloaderError::ProbeRsReadError): if reading fails - pub(crate) fn read_system_attributes_probe( - board_core: &mut Core, - ) -> Result { + /// - Err(TockloaderError::ProbeRsReadError): if reading fails on ProbeRS + /// - Err(TockloaderError::SerialReadError): if reading fails on Serial + pub(crate) async fn read_system_attributes( + conn: &mut dyn IO, + ) -> Result { let mut result = SystemAttributes::new(); // System attributes start at 0x600 and up to 0x9FF. See: // https://book.tockos.org/doc/memory_layout#flash-1 let address = 0x600; - // Each attribute is 64 bytes exactly, and there are 16 slots - let mut buf = [0u8; 64 * 16]; - board_core.read(address, &mut buf)?; + let buf = conn.read(address, 64 * 16).await?; let mut data = buf.chunks(64); @@ -125,9 +121,8 @@ impl SystemAttributes { // TODO(george-cosma): separate kernel attributes from kernel flags. let address = 0x40E; - let mut buf = [0u8; 8]; - board_core.read_8(address, &mut buf)?; + let buf = conn.read(address, 8).await?; let string = String::from_utf8(buf.to_vec()) .map_err(|e| TockError::AttributeParsing(AttributeParseError::InvalidString(e)))?; @@ -137,151 +132,11 @@ impl SystemAttributes { result.bootloader_version = Some(string.to_owned()); // The 100 bytes prior to the application start address are reserved for the kernel attributes and flags - let mut kernel_attr_binary = [0u8; 100]; let kernel_attr_addr = result .appaddr .ok_or(TockError::MissingAttribute("appaddr".to_owned()))? - 100; - board_core.read(kernel_attr_addr, &mut kernel_attr_binary)?; - - let sentinel = bytes_to_string(&kernel_attr_binary[96..100]); - let kernel_version = LittleEndian::read_uint(&kernel_attr_binary[95..96], 1); - - let app_memory_len = LittleEndian::read_u32(&kernel_attr_binary[84..92]); - let app_memory_start = LittleEndian::read_u32(&kernel_attr_binary[80..84]); - - let kernel_binary_start = LittleEndian::read_u32(&kernel_attr_binary[68..72]); - let kernel_binary_len = LittleEndian::read_u32(&kernel_attr_binary[72..76]); - - result.sentinel = Some(sentinel); - result.kernel_version = Some(kernel_version); - result.app_mem_start = Some(app_memory_start); - result.app_mem_len = Some(app_memory_len); - result.kernel_bin_start = Some(kernel_binary_start); - result.kernel_bin_len = Some(kernel_binary_len); - - Ok(result) - } - - /// Read system attributes using a serial connection. A bootloader must be - /// present on this board for this function to work properly. - /// - /// # Parameters - /// - `port`: Serial access, obtained from a - /// [SerialConnection](crate::connection::SerialConnection) - /// - /// # Returns - /// - Ok(result): if attributes were read successfully - /// - Err(TockloaderError::MisconfiguredBoard): if no start address is found or valid - /// - Err(TockloaderError::MisconfiguredBoard): if attributes don't follow the UTF-8 format - /// - Err(TockloaderError::SerialReadError): if reading fails - pub(crate) async fn read_system_attributes_serial( - port: &mut SerialStream, - ) -> Result { - let mut result = SystemAttributes::new(); - - // System attributes start at 0x600 and up to 0x9FF. See: - // https://book.tockos.org/doc/memory_layout#flash-1 - let mut pkt = (0x600_u32).to_le_bytes().to_vec(); - // Each attribute is 64 bytes exactly, and there are 16 slots - let length = (1024_u16).to_le_bytes().to_vec(); - for i in length { - pkt.push(i); - } - - // Read the kernel attributes - let (_, buf) = issue_command( - port, - Command::ReadRange, - pkt, - true, - 64 * 16, - Response::ReadRange, - ) - .await?; - - let mut data = buf.chunks(64); - - for current_slot in 0..data.len() { - let slot_data = match data.next() { - Some(data) => data, - None => break, - }; - - if let Some(decoded_attributes) = decode_attribute(slot_data) { - match current_slot { - 0 => { - result.board = Some(decoded_attributes.value.to_string()); - } - 1 => { - result.arch = Some(decoded_attributes.value.to_string()); - } - 2 => { - // Parse hex string like "0x40000" into actual u64 value - result.appaddr = Some( - u64::from_str_radix( - decoded_attributes - .value - .to_string() - .trim_start_matches("0x"), - 16, - ) - .map_err(|e| { - TockError::AttributeParsing(AttributeParseError::InvalidNumber(e)) - })?, - ); - } - 3 => { - result.boothash = Some(decoded_attributes.value.to_string()); - } - _ => {} - } - } else { - continue; - } - } - - // TODO(george-cosma): separate kernel attributes from kernel flags. - - let mut pkt = (0x40E_u32).to_le_bytes().to_vec(); - let length = (8_u16).to_le_bytes().to_vec(); - for i in length { - pkt.push(i); - } - - // Read bootloader version - let (_, buf) = - issue_command(port, Command::ReadRange, pkt, true, 8, Response::ReadRange).await?; - - let string = String::from_utf8(buf) - .map_err(|e| TockError::AttributeParsing(AttributeParseError::InvalidString(e)))?; - - // Strip null bytes - let string = string.trim_matches(char::from(0)); - result.bootloader_version = Some(string.to_owned()); - - // The 100 bytes prior to the application start address are reserved - // for the kernel attributes and flags - let kernel_attr_addr = (result - .appaddr - .ok_or(TockError::MissingAttribute("appaddr".to_owned()))? - - 100) as u32; - let mut pkt = kernel_attr_addr.to_le_bytes().to_vec(); - let length = (100_u16).to_le_bytes().to_vec(); - for i in length { - pkt.push(i); - } - - // Read kernel flags - let (_, kernel_attr_binary) = issue_command( - port, - Command::ReadRange, - pkt, - true, - 100, - Response::ReadRange, - ) - .await?; + let kernel_attr_binary = conn.read(kernel_attr_addr, 100).await?; let sentinel = bytes_to_string(&kernel_attr_binary[96..100]); let kernel_version = LittleEndian::read_uint(&kernel_attr_binary[95..96], 1); diff --git a/tockloader-lib/src/command_impl/probers/io.rs b/tockloader-lib/src/command_impl/probers/io.rs index dcc2367f..5ccb72a2 100644 --- a/tockloader-lib/src/command_impl/probers/io.rs +++ b/tockloader-lib/src/command_impl/probers/io.rs @@ -56,11 +56,7 @@ impl IOCommands for ProbeRSConnection { if !self.is_open() { return Err(InternalError::ConnectionNotOpen.into()); } - let session = self.session.as_mut().expect("Board must be open"); - - let mut core = session.core(self.target_info.core)?; - - let system_attributes = SystemAttributes::read_system_attributes_probe(&mut core)?; + let system_attributes = SystemAttributes::read_system_attributes(self).await?; Ok(system_attributes) } } diff --git a/tockloader-lib/src/command_impl/serial/io.rs b/tockloader-lib/src/command_impl/serial/io.rs index 6751559b..cbc8ddd4 100644 --- a/tockloader-lib/src/command_impl/serial/io.rs +++ b/tockloader-lib/src/command_impl/serial/io.rs @@ -83,7 +83,7 @@ impl IOCommands for SerialConnection { ping_bootloader_and_wait_for_response(stream).await?; - let system_attributes = SystemAttributes::read_system_attributes_serial(stream).await?; + let system_attributes = SystemAttributes::read_system_attributes(self).await?; Ok(system_attributes) } } From 94e3382d5e8c022d6be705c0cd5e69865f5f83e6 Mon Sep 17 00:00:00 2001 From: Adrian Lungu Date: Tue, 9 Dec 2025 14:21:05 +0200 Subject: [PATCH 3/8] refactor: read_apps_data Signed-off-by: Adrian Lungu --- .../src/attributes/app_attributes.rs | 173 ++---------------- tockloader-lib/src/command_impl/probers/io.rs | 5 +- tockloader-lib/src/command_impl/serial/io.rs | 5 +- 3 files changed, 18 insertions(+), 165 deletions(-) diff --git a/tockloader-lib/src/attributes/app_attributes.rs b/tockloader-lib/src/attributes/app_attributes.rs index b87ae6ba..31686b3e 100644 --- a/tockloader-lib/src/attributes/app_attributes.rs +++ b/tockloader-lib/src/attributes/app_attributes.rs @@ -2,15 +2,12 @@ // SPDX-License-Identifier: Apache-2.0 OR MIT // Copyright OXIDOS AUTOMOTIVE 2024. -use probe_rs::{Core, MemoryInterface}; - use tbf_parser::parse::{parse_tbf_footer, parse_tbf_header, parse_tbf_header_lengths}; use tbf_parser::types::{TbfFooterV2Credentials, TbfHeader}; use tbf_parser::{self}; -use tokio_serial::SerialStream; -use crate::bootloader_serial::{issue_command, Command, Response}; use crate::errors::{TockError, TockloaderError}; +use crate::IO; /// This structure contains all relevant information about a tock application. /// @@ -55,20 +52,19 @@ impl AppAttributes { } /// Retrieve all application attributes from the device's memory using a - /// probe-rs connection. + /// generalized connection. /// /// Applications are layed out in memory sequentially, starting from the /// `appaddr` address. This function will attempt to read all applications /// until it fails to parse. /// /// # Parameters - /// - `board_core`: Core access, obtained from a - /// [ProbeRSConnection](crate::connection::ProbeRSConnection) + /// - `conn` : Either a SerialConnection or a ProbeRSConnection /// - `addr`: The starting address of the first application in memory. /// Board-specific. See also /// [BoardSettings](crate::board_settings::BoardSettings). - pub(crate) fn read_apps_data_probe( - board_core: &mut Core, + pub(crate) async fn read_apps_data( + conn: &mut dyn IO, addr: u64, ) -> Result, TockloaderError> { let mut appaddr: u64 = addr; @@ -78,9 +74,7 @@ impl AppAttributes { // All applications are stored sequentially in memory, so we read until // we fail to parse. loop { - let mut appdata = vec![0u8; 8]; - - board_core.read(appaddr, &mut appdata)?; + let appdata = conn.read(appaddr, 8).await?; let tbf_version: u16; let header_size: u16; @@ -101,16 +95,14 @@ impl AppAttributes { header_size = data.1; total_size = data.2; } - _ => return Ok(apps_details), + _ => break, }; log::debug!( "App #{apps_counter}: TBF version {tbf_version}, header size {header_size}, total size {total_size}", ); - let mut header_data = vec![0u8; header_size as usize]; - - board_core.read(appaddr, &mut header_data)?; + let header_data = conn.read(appaddr, header_size as usize).await?; log::debug!("App #{apps_counter}: Header data: {header_data:?}"); let header = parse_tbf_header(&header_data, tbf_version) .map_err(TockError::InvalidAppTbfHeader)?; @@ -141,149 +133,12 @@ impl AppAttributes { // We don't know the size of the current footer, so we read the // remaining bytes in the application (`footer_offset - // binary_end_offset`) , even if we overread. - let mut appfooter = - vec![0u8; (total_footers_size - (footer_offset - binary_end_offset)) as usize]; - - board_core.read(appaddr + footer_offset as u64, &mut appfooter)?; - - let footer_info = - parse_tbf_footer(&appfooter).map_err(TockError::InvalidAppTbfHeader)?; - - footers.insert(footer_number, TbfFooter::new(footer_info.0, footer_info.1)); - - footer_number += 1; - // we add 4 because that is the size of TL part of the TLV header (2 bytes type + 2 bytes length) - footer_offset += footer_info.1 + 4; - } - - let details: AppAttributes = AppAttributes::new(appaddr, header, footers); - - apps_details.insert(apps_counter, details); - apps_counter += 1; - appaddr += total_size as u64; - } - } - - /// Retrieve all application attributes from the device's memory using a - /// serial connection. - /// - /// Applications are layed out in memory sequentially, starting from the - /// `appaddr` address. This function will attempt to read all applications - /// until it fails to parse. - /// - /// # Parameters - /// - `port`: Serial access, obtained from a - /// [SerialConnection](crate::connection::SerialConnection) - /// - `addr`: The starting address of the first application in memory. - /// Board-specific. See also - /// [BoardSettings](crate::board_settings::BoardSettings). - pub(crate) async fn read_apps_data_serial( - port: &mut SerialStream, - addr: u64, - ) -> Result, TockloaderError> { - let mut appaddr: u64 = addr; - let mut apps_counter = 0; - let mut apps_details: Vec = vec![]; - - // All applications are stored sequentially in memory, so we read until - // we fail to parse. - loop { - // The tockloader protocol only supports 32-bit architectures, - // though in the future support will be extended to 64-bit. - let mut pkt = (appaddr as u32).to_le_bytes().to_vec(); - let length = (8_u16).to_le_bytes().to_vec(); - for i in length { - pkt.push(i); - } - - // Read the first 8 bytes, which is the length of a TLV header. - let (_, appdata) = - issue_command(port, Command::ReadRange, pkt, true, 8, Response::ReadRange).await?; - - let tbf_version: u16; - let header_size: u16; - let total_size: u32; - - // The first 8 bytes of the application data contain the TBF header - // lengths and version. - // - // Note on expect: `read` always fills up the entire buffer, which - // was previously declared as 8 bytes. - match parse_tbf_header_lengths( - &appdata[0..8] - .try_into() - .expect("Buffer length must be at least 8 bytes long."), - ) { - Ok(data) => { - tbf_version = data.0; - header_size = data.1; - total_size = data.2; - } - _ => break, - }; - - log::debug!( - "App #{apps_counter}: TBF version {tbf_version}, header size {header_size}, total size {total_size}", - ); - - let mut pkt = (appaddr as u32).to_le_bytes().to_vec(); - let length = (header_size).to_le_bytes().to_vec(); - for i in length { - pkt.push(i); - } - - // Read the rest of the header - let (_, header_data) = issue_command( - port, - Command::ReadRange, - pkt, - true, - header_size.into(), - Response::ReadRange, - ) - .await?; - - log::debug!("App #{apps_counter}: Header data: {header_data:?}"); - let header = parse_tbf_header(&header_data, tbf_version) - .map_err(TockError::InvalidAppTbfHeader)?; - let binary_end_offset = header.get_binary_end(); - - match &header { - TbfHeader::TbfHeaderV2(_hd) => {} - _ => { - appaddr += total_size as u64; - continue; - } - }; - - let mut footers: Vec = vec![]; - let total_footers_size = total_size - binary_end_offset; - let mut footer_offset = binary_end_offset; - let mut footer_number = 0; - - // Try to parse footers until we reach the end of the application. - while footer_offset < total_size { - // We don't know the size of the current footer, so we read the - // remaining bytes in the application (`footer_offset - - // binary_end_offset`) , even if we overread. - let mut pkt = (appaddr as u32 + footer_offset).to_le_bytes().to_vec(); - let length = ((total_footers_size - (footer_offset - binary_end_offset)) as u16) - .to_le_bytes() - .to_vec(); - for i in length { - pkt.push(i); - } - - // Read the next header (and perhaps data beyond it) - let (_, appfooter) = issue_command( - port, - Command::ReadRange, - pkt, - true, - (total_footers_size - (footer_offset - binary_end_offset)) as usize, - Response::ReadRange, - ) - .await?; + let appfooter = conn + .read( + appaddr + footer_offset as u64, + (total_footers_size - (footer_offset - binary_end_offset)) as usize, + ) + .await?; let footer_info = parse_tbf_footer(&appfooter).map_err(TockError::InvalidAppTbfHeader)?; diff --git a/tockloader-lib/src/command_impl/probers/io.rs b/tockloader-lib/src/command_impl/probers/io.rs index 5ccb72a2..886c1ab8 100644 --- a/tockloader-lib/src/command_impl/probers/io.rs +++ b/tockloader-lib/src/command_impl/probers/io.rs @@ -46,10 +46,7 @@ impl IOCommands for ProbeRSConnection { return Err(InternalError::ConnectionNotOpen.into()); } let start_address = self.get_settings().start_address; - let session = self.session.as_mut().expect("Board must be open"); - let mut core = session.core(self.target_info.core)?; - - AppAttributes::read_apps_data_probe(&mut core, start_address) + AppAttributes::read_apps_data(self, start_address).await } async fn read_system_attributes(&mut self) -> Result { diff --git a/tockloader-lib/src/command_impl/serial/io.rs b/tockloader-lib/src/command_impl/serial/io.rs index cbc8ddd4..2f0a8c69 100644 --- a/tockloader-lib/src/command_impl/serial/io.rs +++ b/tockloader-lib/src/command_impl/serial/io.rs @@ -67,12 +67,13 @@ impl IOCommands for SerialConnection { if !self.is_open() { return Err(InternalError::ConnectionNotOpen.into()); } - let start_address = self.get_settings().start_address; let stream = self.stream.as_mut().expect("Board must be open"); ping_bootloader_and_wait_for_response(stream).await?; - AppAttributes::read_apps_data_serial(stream, start_address).await + let start_address = self.get_settings().start_address; + + AppAttributes::read_apps_data(self, start_address).await } async fn read_system_attributes(&mut self) -> Result { From 543e3a3f1e5705c80bd98e3928a0038b033b516e Mon Sep 17 00:00:00 2001 From: Adrian Lungu Date: Mon, 2 Feb 2026 21:14:11 +0200 Subject: [PATCH 4/8] chore: rebased install_rework Signed-off-by: Adrian Lungu --- tockloader-lib/Cargo.toml | 5 +- tockloader-lib/src/board_settings.rs | 3 + tockloader-lib/src/command_impl/install.rs | 48 +- tockloader-lib/src/command_impl/mod.rs | 1 + .../src/command_impl/reshuffle_apps.rs | 419 ++++++++++++++++++ tockloader-lib/src/connection.rs | 16 +- tockloader-lib/src/known_boards.rs | 2 + tockloader-lib/src/tabs/tab.rs | 62 ++- 8 files changed, 538 insertions(+), 18 deletions(-) create mode 100644 tockloader-lib/src/command_impl/reshuffle_apps.rs diff --git a/tockloader-lib/Cargo.toml b/tockloader-lib/Cargo.toml index e89bc42e..0b9ec571 100644 --- a/tockloader-lib/Cargo.toml +++ b/tockloader-lib/Cargo.toml @@ -9,9 +9,9 @@ edition = "2021" [dependencies] tokio = { version = "1.32.0", features = ["full"] } -tokio-serial = {version = "5.4.4", features = ["libudev"]} +tokio-serial = { version = "5.4.4", features = ["libudev"] } probe-rs = "0.24.0" -tbf-parser = { path = "../tbf-parser"} +tbf-parser = { path = "../tbf-parser" } utf8-decode = "1.0.1" byteorder = "1.5.0" tar = "0.4.41" @@ -21,3 +21,4 @@ serde = { version = "1.0.210", features = ["derive"] } thiserror = "1.0.63" async-trait = "0.1.88" log = "0.4.27" +itertools = "0.14.0" diff --git a/tockloader-lib/src/board_settings.rs b/tockloader-lib/src/board_settings.rs index f9bfec5b..22be548b 100644 --- a/tockloader-lib/src/board_settings.rs +++ b/tockloader-lib/src/board_settings.rs @@ -1,7 +1,9 @@ +#[derive(Clone)] pub struct BoardSettings { pub arch: Option, pub start_address: u64, pub page_size: u64, + pub ram_start_address: u64, } // TODO(george-cosma): Does a default implementation make sense for this? Is a @@ -12,6 +14,7 @@ impl Default for BoardSettings { arch: None, start_address: 0x30000, page_size: 512, + ram_start_address: 0x20000000, } } } diff --git a/tockloader-lib/src/command_impl/install.rs b/tockloader-lib/src/command_impl/install.rs index cae2b7ac..5737f19c 100644 --- a/tockloader-lib/src/command_impl/install.rs +++ b/tockloader-lib/src/command_impl/install.rs @@ -1,13 +1,51 @@ use async_trait::async_trait; -use crate::connection::TockloaderConnection; -use crate::errors::TockloaderError; +use crate::attributes::app_attributes::AppAttributes; +use crate::command_impl::reshuffle_apps::{create_pkt, reshuffle_apps, TockApp}; +use crate::connection::{Connection, TockloaderConnection}; +use crate::errors::{InternalError, TockloaderError}; use crate::tabs::tab::Tab; -use crate::CommandInstall; +use crate::{CommandInstall, CommandList, IO}; #[async_trait] impl CommandInstall for TockloaderConnection { - async fn install_app(&mut self, _tab: Tab) -> Result<(), TockloaderError> { - todo!() + async fn install_app(&mut self, tab: Tab) -> Result<(), TockloaderError> { + let settings = self.get_settings(); + let app_attributes_list: Vec = self.list().await?; + let mut tock_app_list = app_attributes_list + .iter() + .map(|app| TockApp::from_app_attributes(app)) + .collect::>(); + log::info!("tock apps len {:?}", tock_app_list.len()); + + // obtain the binaries in a vector + let mut app_binaries: Vec> = Vec::new(); + + let mut address = settings.start_address; + for app in app_attributes_list.iter() { + app_binaries.push( + self.read(address, app.tbf_header.total_size() as usize) + .await + .unwrap(), + ); + address += app.tbf_header.total_size() as u64; + } + + let app = TockApp::from_tab(&tab, &settings).unwrap(); + + tock_app_list.push(app.clone()); + + let configuration = + reshuffle_apps(&settings, tock_app_list).ok_or(TockloaderError::Internal( + InternalError::MisconfiguredBoardSettings("Can't fit new app".to_string()), + ))?; + + // create the pkt, this contains all the binaries in a vec + let pkt = create_pkt(configuration, app_binaries, Some(tab), &settings); + + log::debug!("pkt len {}", pkt.len()); + // write the pkt + let _ = self.write(settings.start_address, &pkt).await; + Ok(()) } } diff --git a/tockloader-lib/src/command_impl/mod.rs b/tockloader-lib/src/command_impl/mod.rs index 84d774ea..22d488e1 100644 --- a/tockloader-lib/src/command_impl/mod.rs +++ b/tockloader-lib/src/command_impl/mod.rs @@ -4,4 +4,5 @@ pub mod info; pub mod install; pub mod list; pub mod probers; +pub mod reshuffle_apps; pub mod serial; diff --git a/tockloader-lib/src/command_impl/reshuffle_apps.rs b/tockloader-lib/src/command_impl/reshuffle_apps.rs new file mode 100644 index 00000000..5b9b002b --- /dev/null +++ b/tockloader-lib/src/command_impl/reshuffle_apps.rs @@ -0,0 +1,419 @@ +use itertools::Itertools; +use log::warn; + +use crate::attributes::app_attributes::AppAttributes; +use crate::board_settings::BoardSettings; +use crate::errors::{InternalError, TabError}; +use crate::tabs::tab::{Tab, TbfFile}; +use tbf_parser::parse::{parse_tbf_header, parse_tbf_header_lengths}; + +const ALIGNMENT: u64 = 1024; + +#[derive(Clone, Debug)] +pub enum TockApp { + Flexible(FlexibleApp), + Fixed(FixedApp), +} + +#[derive(Clone, Debug)] +pub struct FlexibleApp { + installed: bool, + idx: Option, + size: u64, +} + +#[derive(Clone, Debug)] +pub struct FixedApp { + installed: bool, + idx: Option, + // flash: u64 and ram: u64 + compatible_addresses: Vec>, + size: u64, +} + +impl TockApp { + pub fn replace_idx(&mut self, new_idx: usize) -> Option { + match self { + TockApp::Flexible(flexible_app) => flexible_app.idx.replace(new_idx), + TockApp::Fixed(fixed_app) => fixed_app.idx.replace(new_idx), + } + } + + pub fn from_app_attributes(app_attributes: &AppAttributes) -> TockApp { + if let (Some(flash_addr), Some(ram_addr)) = ( + app_attributes.tbf_header.get_fixed_address_flash(), + app_attributes.tbf_header.get_fixed_address_ram(), + ) { + let mut aligned_adr = align_down(flash_addr as u64); + if aligned_adr < 0x00040000 { + aligned_adr = 0x00040000; + } + // let flash_header = flash_addr + app_attributes.tbf_header.total_size() as u32; + log::info!( + "found fixed flash 0x{:08x} and fixed ram 0x{:08x}", + aligned_adr, + ram_addr + ); + // panic!(); + // cortex-m4.0x00040000.0x20008000 + // cortex-m4.0x00040000.0x20008000 + log::info!("setting flash address form header {:#x}", aligned_adr); + TockApp::Fixed(FixedApp { + installed: true, + idx: None, + compatible_addresses: vec![(Some((aligned_adr, ram_addr as u64)))], + size: app_attributes.tbf_header.total_size() as u64, + }) + } else { + TockApp::Flexible(FlexibleApp { + installed: true, + idx: None, + size: app_attributes.tbf_header.total_size() as u64, + }) + } + } + + /// This function returns an instance of TockApp + pub fn from_tab(tab: &Tab, settings: &BoardSettings) -> Option { + // extract the binary + // this should be changed to accomodate candidate_addresses + let binary = tab + .extract_binary(settings.arch.clone().unwrap()) + .expect("invalid arch"); + + // extract relevant data from the header + let (tbf_version, header_len, total_size) = match parse_tbf_header_lengths( + &binary[0..8] + .try_into() + .expect("Buffer length must be at least 8 bytes long."), + ) { + Ok((tbf_version, header_len, total_size)) if header_len != 0 => { + (tbf_version, header_len, total_size) + } + _ => return None, + }; + + // turn the buffer slice into a TbfHeader instance + let header = + parse_tbf_header(&binary[0..header_len as usize], tbf_version).expect("invalid header"); + + if let Some(_) = header.get_fixed_address_flash() { + // addr = align_down(addr as u64) as u32; + // if addr < settings.start_address as u32 { + // // this rust app should not be here + // panic!( + // "This rust app starts at {addr:#x}, while the board's start_address is {:#x}", + // settings.start_address + // ) + // } + // turns out that fixed address is a loosely-used term, address has to be aligned down to a multiple of 1024 bytes + // let address = align_down(addr as u64); + + Some(TockApp::Fixed(FixedApp { + installed: false, + idx: None, + compatible_addresses: tab.filter_tbfs(settings).unwrap(), // (adi): change this when tbf selector gets merged + size: total_size as u64, + })) + } else { + Some(TockApp::Flexible(FlexibleApp { + installed: false, + idx: None, + size: total_size as u64, + })) + } + } +} + +impl FixedApp { + fn as_index(&self, ram_address: Option, install_address: u64) -> Index { + Index { + installed: self.installed, + idx: self.idx, + fixed: true, + ram_address, + address: install_address, + size: self.size, + } + } +} + +impl FlexibleApp { + fn as_index(&self, ram_address: Option, install_address: u64) -> Index { + Index { + installed: self.installed, + idx: self.idx, + fixed: false, + ram_address, + address: install_address, + size: self.size, + } + } +} + +#[derive(Debug, Clone)] +pub struct Index { + installed: bool, + idx: Option, + fixed: bool, + ram_address: Option, + address: u64, + size: u64, +} + +pub fn reshuffle_apps( + settings: &BoardSettings, + mut installed_apps: Vec, +) -> Option> { + // On the first pass, we must assign every app its original index, so we can + // keep track of it. + for (idx, app) in installed_apps.iter_mut().enumerate() { + if app.replace_idx(idx).is_some() { + warn!("Encountered existing index in TockApp at the start of reorder_apps."); + } + } + log::debug!("installed apps? {:#x?}", installed_apps); + + let mut rust_apps: Vec<&mut FixedApp> = Vec::new(); + let mut c_apps: Vec<&mut FlexibleApp> = Vec::new(); + + for app in &mut installed_apps { + match app { + TockApp::Flexible(flexible_app) => c_apps.push(flexible_app), + TockApp::Fixed(fixed_app) => rust_apps.push(fixed_app), + } + } + + // This places rust apps that were not installed in the front, so the candidate + // addresses can be used. Otherwise, the algorithm will just look for the next + // available address, leaving a lot of space in the front. This can happen + // after uninstalling a rust app + rust_apps.sort_by_key(|app| app.compatible_addresses[0].unwrap().0); + log::info!("sorted rust apps {:#x?}", rust_apps); + // panic!(); + + for app in &mut rust_apps { + if app.compatible_addresses.is_empty() { + warn!("Can not reorder apps since fixed application has no candidate addresses!"); + return None; + } + } + + // make permutations only for the c apps, as their order can be changed + let mut permutations = (0..c_apps.len()).permutations(c_apps.len()); + + let mut min_padding = usize::MAX; + let mut saved_configuration: Vec = Vec::new(); + + for _ in 0..100_000 { + // use just 100k permutations, or else we'll be here for a while + if let Some(order) = permutations.next() { + let mut total_padding: usize = 0; + let mut permutation_index: usize = 0; + let mut rust_index: usize = 0; + let mut reordered_apps: Vec = Vec::new(); + let mut compatible_index: usize = 0; + loop { + let insert_c: bool; // every iteration will insert an app, or break if there are none left + + // start either where the last app ends, or at start address if there are no apps + let address = reordered_apps + .last() + .map_or(settings.start_address, |app| app.address + app.size); + + if order.get(permutation_index).is_some() { + // we have a C app + if rust_apps.get(rust_index).is_some() { + // we also have a rust app, insert C app only if it fits + loop { + if rust_apps[rust_index].compatible_addresses[compatible_index] + .expect("No available binary (idk)") + .0 + >= address + { + break; + } else { + compatible_index += 1; + } + } + insert_c = c_apps[order[permutation_index]].size + <= rust_apps[rust_index].compatible_addresses[compatible_index] + .expect("No candidate (1)") + .0 + - address; + } else { + // we have only a C app, insert it accordingly + insert_c = true; + } + } else { + // we don't have a c app + if rust_apps.get(rust_index).is_some() { + loop { + log::info!( + "comparing rust app addr {:#x?} and address {:#x?}", + rust_apps[rust_index].compatible_addresses[compatible_index] + .unwrap() + .0, + address + ); + if rust_apps[rust_index].compatible_addresses[compatible_index] + .expect("No available binary (idk)") + .0 + >= address + { + break; + } else { + compatible_index += 1; + } + } + // we have a rust app, insert it + insert_c = false; + } else { + // we don't have any app, break? + break; + } + } + + let mut start_address = reordered_apps + .last() + .map_or(settings.start_address, |app| app.address + app.size); + + let needed_padding = if insert_c { + if !start_address.is_multiple_of(settings.page_size) { + // c app needs to be inserted at a multiple of page_size + settings.page_size - start_address % settings.page_size + } else { + 0 + } + } else { + // rust app needs to be inserted at a fixed address, pad until there + rust_apps[rust_index].compatible_addresses[compatible_index] + .expect("No compatible address! (3)") + .0 + - start_address + }; + + if needed_padding > 0 { + // insert a padding + total_padding += needed_padding as usize; + reordered_apps.push(Index { + installed: false, + idx: None, + fixed: false, + ram_address: None, + address: start_address, + size: needed_padding, + }); + + start_address += needed_padding as u64; + } + + if insert_c { + // insert the c app, also change its address + let c_app = c_apps[order[permutation_index]].as_index(None, start_address); + if c_app.idx.is_none() { + panic!("C app has no index assigned!"); + } + + reordered_apps.push(c_app); + permutation_index += 1; + } else { + // insert the rust app, don't change its address because it is fixed + let rust_app = rust_apps[rust_index].as_index( + Some( + rust_apps[rust_index].compatible_addresses[compatible_index] + .expect("No compatible address! (4)") + .1, + ), + rust_apps[rust_index].compatible_addresses[compatible_index] + .expect("No compatible address! (4)") + .0, + ); + log::debug!( + "rust app flash {:#x?}, rust app ram {:#x?}", + rust_app.address, + rust_app.ram_address + ); + if rust_app.idx.is_none() { + panic!("Rust app has no index assigned!"); + } + + reordered_apps.push(rust_app); + rust_index += 1; + } + } + + // find the configuration that uses the minimum padding + if total_padding < min_padding { + min_padding = total_padding; + saved_configuration = reordered_apps.clone(); + if min_padding == 0 { + break; + } + } + } else { + break; + } + } + log::info!("obtained config {:#x?}", saved_configuration); + Some(saved_configuration) +} + +/// This function returns the binary for a padding +fn create_padding(size: u32) -> Vec { + let mut buf: Vec = Vec::new(); + buf.extend_from_slice(&u16::to_le_bytes(2u16)); // tbf version 2 + buf.extend_from_slice(&u16::to_le_bytes(16u16)); // header size is 16 + buf.extend_from_slice(&u32::to_le_bytes(size)); // total_size is size + let mut checksum = 0; + for chunk in buf.chunks_exact(4) { + let word = u32::from_le_bytes(chunk.try_into().unwrap()); + checksum ^= word; + } + buf.extend_from_slice(&u32::to_le_bytes(checksum)); + while buf.len() < size as usize { + buf.push(0x0u8); + } + buf +} + +/// This function takes a rust app's fixed address and aligns it down to ALIGNMENT (1024 currently) +fn align_down(address: u64) -> u64 { + address - address % ALIGNMENT +} + +/// This function creates the full binary that will be written +pub fn create_pkt( + configuration: Vec, + mut app_binaries: Vec>, + tab: Option, + settings: &BoardSettings, +) -> Vec { + let mut pkt: Vec = Vec::new(); + for item in configuration.iter() { + if item.idx.is_none() { + // write padding binary + let mut buf = create_padding(item.size as u32); + pkt.append(&mut buf); + } else { + match &item.installed { + true => pkt.append(&mut app_binaries[item.idx.unwrap()]), + false => { + let mut arch: String = settings.arch.clone().unwrap(); + // if ram is set, this is a rust app, we need to reconstruct the arch and then + // read the binary from the tab + if item.ram_address.is_some() { + arch = format!( + "{}.0x{:08x}.0x{:08x}", + arch, + item.address, + item.ram_address.unwrap() + ); + } + pkt.append(&mut tab.as_ref().unwrap().extract_binary(arch).unwrap()); + } + } + } + } + pkt +} diff --git a/tockloader-lib/src/connection.rs b/tockloader-lib/src/connection.rs index bb3cab38..5ae56c82 100644 --- a/tockloader-lib/src/connection.rs +++ b/tockloader-lib/src/connection.rs @@ -52,7 +52,7 @@ pub trait Connection { /// `open` or any other method is undefined behavior. async fn close(&mut self) -> Result<(), TockloaderError>; fn is_open(&self) -> bool; - fn get_settings(&self) -> &BoardSettings; + fn get_settings(&self) -> BoardSettings; } pub struct ProbeRSConnection { @@ -62,7 +62,7 @@ pub struct ProbeRSConnection { pub(crate) target_info: ProbeTargetInfo, /// Only used for opening a new connection debug_probe: DebugProbeInfo, - settings: BoardSettings, + pub settings: BoardSettings, } impl ProbeRSConnection { @@ -102,8 +102,8 @@ impl Connection for ProbeRSConnection { self.session.is_some() } - fn get_settings(&self) -> &BoardSettings { - &self.settings + fn get_settings(&self) -> BoardSettings { + self.settings.clone() } } @@ -114,7 +114,7 @@ pub struct SerialConnection { pub(crate) target_info: SerialTargetInfo, /// Path to the serial port. This is only used for opening a new connection. port: String, - settings: BoardSettings, + pub settings: BoardSettings, } impl SerialConnection { @@ -165,8 +165,8 @@ impl Connection for SerialConnection { self.stream.is_some() } - fn get_settings(&self) -> &BoardSettings { - &self.settings + fn get_settings(&self) -> BoardSettings { + self.settings.clone() } } @@ -213,7 +213,7 @@ impl Connection for TockloaderConnection { } } - fn get_settings(&self) -> &BoardSettings { + fn get_settings(&self) -> BoardSettings { match self { TockloaderConnection::ProbeRS(conn) => conn.get_settings(), TockloaderConnection::Serial(conn) => conn.get_settings(), diff --git a/tockloader-lib/src/known_boards.rs b/tockloader-lib/src/known_boards.rs index 6a986273..009992de 100644 --- a/tockloader-lib/src/known_boards.rs +++ b/tockloader-lib/src/known_boards.rs @@ -26,6 +26,7 @@ impl KnownBoard for NucleoF4 { arch: Some("cortex-m4".to_string()), start_address: 0x08040000, page_size: 512, + ram_start_address: 0x20000000, } } } @@ -49,6 +50,7 @@ impl KnownBoard for MicrobitV2 { arch: Some("cortex-m4".to_string()), start_address: 0x00040000, page_size: 512, + ram_start_address: 0x20000000, } } } diff --git a/tockloader-lib/src/tabs/tab.rs b/tockloader-lib/src/tabs/tab.rs index fe69d54a..d11da539 100644 --- a/tockloader-lib/src/tabs/tab.rs +++ b/tockloader-lib/src/tabs/tab.rs @@ -2,13 +2,14 @@ // SPDX-License-Identifier: Apache-2.0 OR MIT // Copyright OXIDOS AUTOMOTIVE 2024. +use crate::board_settings::BoardSettings; use crate::errors::{TabError, TockloaderError}; use crate::tabs::metadata::Metadata; use std::fs::File; use std::io::Read; use tar::Archive; -struct TbfFile { +pub struct TbfFile { pub filename: String, pub data: Vec, } @@ -75,9 +76,64 @@ impl Tab { } } - pub fn extract_binary(&self, arch: &str) -> Result, TockloaderError> { + /// This function returns all the compatible binaries for a given tab + /// + /// Vector components: + /// - binary (Vec) + /// - start_address: u64 + /// - ram_start_address: u64 + pub fn filter_tbfs( + &self, + settings: &BoardSettings, + ) -> Result>, TockloaderError> { + // save the file + // also save flash start and ram start for comparing easily later + let mut compatible_tbfs: Vec> = Vec::new(); for file in &self.tbf_files { - if file.filename.starts_with(arch) { + let (arch, flash, ram) = Self::split_arch(file.filename.to_string()); + // check if we have the same arch + // check if flash and ram fit + if flash != 0 && ram != 0 { + if arch.starts_with(settings.arch.as_ref().unwrap()) + && flash >= settings.start_address + && ram >= settings.ram_start_address + { + log::info!("rust, pushed arch {arch}, flash {flash:#x}, ram {ram:#x}"); + compatible_tbfs.push(Some((flash, ram))); + } + } + // how about we don't do anything on else? + // } else if arch.starts_with(settings.arch.as_ref().unwrap()) { + // // this happens for C apps, we'll have + // // arch = "cortex-m4.tbf" + // // without any flash and ram values + // compatible_tbfs.push(None); + // } + } + Ok(compatible_tbfs) + } + + fn split_arch(filename: String) -> (String, u64, u64) { + // filename is always formatted like this: + // "cortex-m0.0x10020000.0x20004000.tab" + // splitting by .0x will give us "arch", "flash start", "ram start.tab" + // 3 items + // log::info!("filename {filename}"); + let data: Vec<&str> = filename.split(".0x").collect(); + if data.len() == 3 { + let flashaddr: u64 = u64::from_str_radix(data[1], 16).unwrap(); + // split the ram address again because it also contains .tab + // take the first item of the tuple + let ramaddr: u64 = u64::from_str_radix(data[2].split_once(".").unwrap().0, 16).unwrap(); + (data[0].to_string(), flashaddr, ramaddr) + } else { + (data[0].to_string(), 0, 0) + } + } + + pub fn extract_binary(&self, arch: String) -> Result, TockloaderError> { + for file in &self.tbf_files { + if file.filename.starts_with(&arch) { return Ok(file.data.clone()); } } From 757cdc125d1c8d7d89842210c0b580918de9cfb8 Mon Sep 17 00:00:00 2001 From: Adrian Lungu Date: Tue, 3 Mar 2026 22:56:48 +0200 Subject: [PATCH 5/8] reshuffle with mixed apps Signed-off-by: Adrian Lungu --- .../src/command_impl/reshuffle_apps.rs | 324 ++++++++++++------ 1 file changed, 212 insertions(+), 112 deletions(-) diff --git a/tockloader-lib/src/command_impl/reshuffle_apps.rs b/tockloader-lib/src/command_impl/reshuffle_apps.rs index 5b9b002b..d3e1d000 100644 --- a/tockloader-lib/src/command_impl/reshuffle_apps.rs +++ b/tockloader-lib/src/command_impl/reshuffle_apps.rs @@ -188,7 +188,119 @@ pub fn reshuffle_apps( // addresses can be used. Otherwise, the algorithm will just look for the next // available address, leaving a lot of space in the front. This can happen // after uninstalling a rust app + + for app in &rust_apps { + print!("rust app address(es): "); + for address in &app.compatible_addresses { + if address.is_some() { + print!("[{:#x}, {:#x}]", address.unwrap().0, address.unwrap().1); + } + } + println!(); + } + rust_apps.sort_by_key(|app| app.compatible_addresses[0].unwrap().0); + + let mut initial_configuration: Vec = Vec::new(); + let mut new_rust_app: Option<&FixedApp> = None; + for app in &rust_apps { + if app.installed { + initial_configuration.push(app.as_index( + Some(app.compatible_addresses[0].unwrap().1), + app.compatible_addresses[0].unwrap().0, + )); + } else { + new_rust_app = Some(app); + } + } + + log::info!("first intial configuration: "); + for app in &initial_configuration { + log::info!( + "app idx {}, addr {:#x}, size {}", + app.idx.unwrap(), + app.address, + app.size + ); + } + + if let Some(new_app) = new_rust_app { + let mut found: bool = false; + for candidate in new_app.compatible_addresses.iter().flatten() { + let (flash_addr, ram_addr) = *candidate; + let new_start = flash_addr; + let new_end = flash_addr + new_app.size; + log::info!( + "checking for new_start {:#x}, new_end {:#x}", + new_start, + new_end + ); + + // Check before first element + if initial_configuration.is_empty() { + initial_configuration.push(new_app.as_index(Some(ram_addr), flash_addr)); + found = true; + break; + } + + // Check gap before first installed app + let first = &initial_configuration[0]; + if new_end <= first.address { + initial_configuration.insert(0, new_app.as_index(Some(ram_addr), flash_addr)); + found = true; + break; + } + + // Check gaps between apps + for i in 0..initial_configuration.len() - 1 { + let current = &initial_configuration[i]; + let next = &initial_configuration[i + 1]; + + let gap_start = current.address + current.size; + let gap_end = next.address; + + if new_start >= gap_start && new_end <= gap_end { + initial_configuration + .insert(i + 1, new_app.as_index(Some(ram_addr), flash_addr)); + found = true; + break; + } + } + + // Check after last app + let last = initial_configuration.last().unwrap(); + let gap_start = last.address + last.size; + + log::info!( + "checking for last gap {:#x}, obtained with addr {:#x}, size {}", + gap_start, + last.address, + last.size + ); + + if new_start >= gap_start { + initial_configuration.push(new_app.as_index(Some(ram_addr), flash_addr)); + found = true; + break; + } + } + + if !found { + warn!("No suitable space found for new app"); + return None; + } + } + + println!("final intial configuration: "); + for app in &initial_configuration { + println!( + "app idx {}, addr {:#x}, size {}", + app.idx.unwrap(), + app.address, + app.size + ); + } + log::info!("sorted rust apps {:#x?}", rust_apps); // panic!(); @@ -211,136 +323,123 @@ pub fn reshuffle_apps( let mut total_padding: usize = 0; let mut permutation_index: usize = 0; let mut rust_index: usize = 0; - let mut reordered_apps: Vec = Vec::new(); + let mut reordered_apps: Vec = initial_configuration.clone(); let mut compatible_index: usize = 0; - loop { - let insert_c: bool; // every iteration will insert an app, or break if there are none left - - // start either where the last app ends, or at start address if there are no apps - let address = reordered_apps - .last() - .map_or(settings.start_address, |app| app.address + app.size); - - if order.get(permutation_index).is_some() { - // we have a C app - if rust_apps.get(rust_index).is_some() { - // we also have a rust app, insert C app only if it fits - loop { - if rust_apps[rust_index].compatible_addresses[compatible_index] - .expect("No available binary (idk)") - .0 - >= address - { - break; - } else { - compatible_index += 1; - } - } - insert_c = c_apps[order[permutation_index]].size - <= rust_apps[rust_index].compatible_addresses[compatible_index] - .expect("No candidate (1)") - .0 - - address; + + for index in order { + let c_app = &c_apps[index]; + let insert_size = c_app.size; + + if reordered_apps.is_empty() { + reordered_apps.push(c_app.as_index(None, settings.start_address)); + + continue; + } + + log::info!("checking for intermediate"); + let mut inserted = false; + + for i in 0..reordered_apps.len() - 1 { + let base = reordered_apps[i].address + reordered_apps[i].size; + let gap = reordered_apps[i + 1].address - base; + + let needed_padding = if !base.is_multiple_of(settings.page_size) { + settings.page_size - base % settings.page_size } else { - // we have only a C app, insert it accordingly - insert_c = true; - } - } else { - // we don't have a c app - if rust_apps.get(rust_index).is_some() { - loop { - log::info!( - "comparing rust app addr {:#x?} and address {:#x?}", - rust_apps[rust_index].compatible_addresses[compatible_index] - .unwrap() - .0, - address + 0 + }; + + let final_addr = base + needed_padding; + + if insert_size + needed_padding <= gap { + if needed_padding > 0 { + total_padding += needed_padding as usize; + + reordered_apps.insert( + i + 1, + Index { + installed: false, + idx: None, + fixed: false, + ram_address: None, + address: base, + size: needed_padding, + }, ); - if rust_apps[rust_index].compatible_addresses[compatible_index] - .expect("No available binary (idk)") - .0 - >= address - { - break; - } else { - compatible_index += 1; - } + + reordered_apps.insert(i + 2, c_app.as_index(None, final_addr)); + } else { + reordered_apps.insert(i + 1, c_app.as_index(None, final_addr)); } - // we have a rust app, insert it - insert_c = false; - } else { - // we don't have any app, break? + + inserted = true; break; } } - let mut start_address = reordered_apps - .last() - .map_or(settings.start_address, |app| app.address + app.size); + if !inserted { + let last = reordered_apps.last().unwrap(); + let gap_start = last.address + last.size; + + log::info!( + "checking for last gap {:#x}, obtained with addr {:#x}, size {}", + gap_start, + last.address, + last.size + ); - let needed_padding = if insert_c { - if !start_address.is_multiple_of(settings.page_size) { + let needed_padding = if !gap_start.is_multiple_of(settings.page_size) { // c app needs to be inserted at a multiple of page_size - settings.page_size - start_address % settings.page_size + settings.page_size - gap_start % settings.page_size } else { 0 + }; + + if needed_padding > 0 { + // insert a padding + total_padding += needed_padding as usize; + reordered_apps.push(Index { + installed: false, + idx: None, + fixed: false, + ram_address: None, + address: settings.start_address + insert_size, + size: needed_padding, + }); + reordered_apps.push(c_app.as_index(None, gap_start + needed_padding)); + total_padding += needed_padding as usize; + } else { + reordered_apps.push(c_app.as_index(None, gap_start)); } - } else { - // rust app needs to be inserted at a fixed address, pad until there - rust_apps[rust_index].compatible_addresses[compatible_index] - .expect("No compatible address! (3)") - .0 - - start_address - }; - - if needed_padding > 0 { - // insert a padding - total_padding += needed_padding as usize; - reordered_apps.push(Index { - installed: false, - idx: None, - fixed: false, - ram_address: None, - address: start_address, - size: needed_padding, - }); - - start_address += needed_padding as u64; } + } - if insert_c { - // insert the c app, also change its address - let c_app = c_apps[order[permutation_index]].as_index(None, start_address); - if c_app.idx.is_none() { - panic!("C app has no index assigned!"); - } + log::info!( + "obtained config before padding check {:#x?}", + reordered_apps + ); - reordered_apps.push(c_app); - permutation_index += 1; - } else { - // insert the rust app, don't change its address because it is fixed - let rust_app = rust_apps[rust_index].as_index( - Some( - rust_apps[rust_index].compatible_addresses[compatible_index] - .expect("No compatible address! (4)") - .1, - ), - rust_apps[rust_index].compatible_addresses[compatible_index] - .expect("No compatible address! (4)") - .0, + let mut i = 0; + while i < reordered_apps.len() - 1 { + let gap = reordered_apps[i + 1].address + - (reordered_apps[i].address + reordered_apps[i].size); + + if gap > 0 { + reordered_apps.insert( + i + 1, + Index { + installed: false, + idx: None, + fixed: false, + ram_address: None, + address: reordered_apps[i].address + reordered_apps[i].size, + size: gap, + }, ); - log::debug!( - "rust app flash {:#x?}, rust app ram {:#x?}", - rust_app.address, - rust_app.ram_address - ); - if rust_app.idx.is_none() { - panic!("Rust app has no index assigned!"); - } - - reordered_apps.push(rust_app); - rust_index += 1; + i += 1; // skip over inserted padding } + + i += 1; } // find the configuration that uses the minimum padding @@ -356,6 +455,7 @@ pub fn reshuffle_apps( } } log::info!("obtained config {:#x?}", saved_configuration); + // panic!(); Some(saved_configuration) } From 5c795ee549a3cfcaaed4abd4be1d06bb8b87b2f9 Mon Sep 17 00:00:00 2001 From: Adrian Lungu Date: Tue, 14 Apr 2026 14:30:51 +0300 Subject: [PATCH 6/8] chore: reshuffle tests Signed-off-by: Adrian Lungu --- .../src/command_impl/reshuffle_apps.rs | 38 +-- tockloader-lib/tests/reshuffle_apps.rs | 249 ++++++++++++++++++ 2 files changed, 263 insertions(+), 24 deletions(-) create mode 100644 tockloader-lib/tests/reshuffle_apps.rs diff --git a/tockloader-lib/src/command_impl/reshuffle_apps.rs b/tockloader-lib/src/command_impl/reshuffle_apps.rs index d3e1d000..839b15f6 100644 --- a/tockloader-lib/src/command_impl/reshuffle_apps.rs +++ b/tockloader-lib/src/command_impl/reshuffle_apps.rs @@ -3,8 +3,7 @@ use log::warn; use crate::attributes::app_attributes::AppAttributes; use crate::board_settings::BoardSettings; -use crate::errors::{InternalError, TabError}; -use crate::tabs::tab::{Tab, TbfFile}; +use crate::tabs::tab::Tab; use tbf_parser::parse::{parse_tbf_header, parse_tbf_header_lengths}; const ALIGNMENT: u64 = 1024; @@ -17,18 +16,18 @@ pub enum TockApp { #[derive(Clone, Debug)] pub struct FlexibleApp { - installed: bool, - idx: Option, - size: u64, + pub installed: bool, + pub idx: Option, + pub size: u64, } #[derive(Clone, Debug)] pub struct FixedApp { - installed: bool, - idx: Option, + pub installed: bool, + pub idx: Option, // flash: u64 and ram: u64 - compatible_addresses: Vec>, - size: u64, + pub compatible_addresses: Vec>, + pub size: u64, } impl TockApp { @@ -130,7 +129,6 @@ impl FixedApp { Index { installed: self.installed, idx: self.idx, - fixed: true, ram_address, address: install_address, size: self.size, @@ -143,7 +141,6 @@ impl FlexibleApp { Index { installed: self.installed, idx: self.idx, - fixed: false, ram_address, address: install_address, size: self.size, @@ -151,14 +148,13 @@ impl FlexibleApp { } } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] pub struct Index { - installed: bool, - idx: Option, - fixed: bool, - ram_address: Option, - address: u64, - size: u64, + pub installed: bool, + pub idx: Option, + pub ram_address: Option, + pub address: u64, + pub size: u64, } pub fn reshuffle_apps( @@ -321,10 +317,7 @@ pub fn reshuffle_apps( // use just 100k permutations, or else we'll be here for a while if let Some(order) = permutations.next() { let mut total_padding: usize = 0; - let mut permutation_index: usize = 0; - let mut rust_index: usize = 0; let mut reordered_apps: Vec = initial_configuration.clone(); - let mut compatible_index: usize = 0; for index in order { let c_app = &c_apps[index]; @@ -360,7 +353,6 @@ pub fn reshuffle_apps( Index { installed: false, idx: None, - fixed: false, ram_address: None, address: base, size: needed_padding, @@ -401,7 +393,6 @@ pub fn reshuffle_apps( reordered_apps.push(Index { installed: false, idx: None, - fixed: false, ram_address: None, address: settings.start_address + insert_size, size: needed_padding, @@ -430,7 +421,6 @@ pub fn reshuffle_apps( Index { installed: false, idx: None, - fixed: false, ram_address: None, address: reordered_apps[i].address + reordered_apps[i].size, size: gap, diff --git a/tockloader-lib/tests/reshuffle_apps.rs b/tockloader-lib/tests/reshuffle_apps.rs new file mode 100644 index 00000000..82065940 --- /dev/null +++ b/tockloader-lib/tests/reshuffle_apps.rs @@ -0,0 +1,249 @@ +use tockloader_lib::{board_settings::BoardSettings, command_impl::reshuffle_apps::*}; + +#[test] +fn install_new_c_app() { + let settings: &BoardSettings = &BoardSettings { + arch: Some("cortex-m4".to_string()), + start_address: 0x00040000, + page_size: 512, + ram_start_address: 0x20000000, + }; + + let c_app_1: TockApp = TockApp::Flexible(FlexibleApp { + installed: true, + idx: None, + size: 0x2000, + }); + + let c_app_2: TockApp = TockApp::Flexible(FlexibleApp { + installed: true, + idx: None, + size: 0x2000, + }); + + let c_app_3: TockApp = TockApp::Flexible(FlexibleApp { + installed: false, + idx: None, + size: 0x2000, + }); + + let apps: Vec = vec![c_app_1, c_app_2, c_app_3]; + + let reshuffled_apps = reshuffle_apps(settings, apps); + + let correct_config: Option> = Some(vec![ + Index { + installed: true, + idx: Some(0x0), + ram_address: None, + address: 0x40000, + size: 0x2000, + }, + Index { + installed: true, + idx: Some(0x1), + ram_address: None, + address: 0x42000, + size: 0x2000, + }, + Index { + installed: false, + idx: Some(0x2), + ram_address: None, + address: 0x44000, + size: 0x2000, + }, + ]); + assert_eq!(reshuffled_apps, correct_config); +} + +#[test] +fn install_new_rust_app() { + let settings: &BoardSettings = &BoardSettings { + arch: Some("cortex-m4".to_string()), + start_address: 0x00040000, + page_size: 512, + ram_start_address: 0x20000000, + }; + + let rust_app: TockApp = TockApp::Fixed(FixedApp { + installed: false, + idx: None, + compatible_addresses: vec![ + Some((0x40000, 0x20008000)), + Some((0x42000, 0x2000a000)), + Some((0x48000, 0x20010000)), + Some((0x80000, 0x20006000)), + Some((0x88000, 0x2000e000)), + ], + size: 0x2000, + }); + + let apps: Vec = vec![rust_app]; + + let reshuffled_apps = reshuffle_apps(settings, apps); + + let correct_config: Option> = Some(vec![Index { + installed: false, + idx: Some(0x0), + ram_address: Some(0x20008000), + address: 0x40000, + size: 0x2000, + }]); + assert_eq!(reshuffled_apps, correct_config); +} + +#[test] +fn install_more_rust_apps() { + let settings: &BoardSettings = &BoardSettings { + arch: Some("cortex-m4".to_string()), + start_address: 0x00040000, + page_size: 512, + ram_start_address: 0x20000000, + }; + + let rust_app_1: TockApp = TockApp::Fixed(FixedApp { + installed: true, + idx: None, + compatible_addresses: vec![Some((0x40000, 0x20008000))], + size: 0x2000, + }); + + let rust_app_2: TockApp = TockApp::Fixed(FixedApp { + installed: true, + idx: None, + compatible_addresses: vec![Some((0x42000, 0x2000a000))], + size: 0x2000, + }); + + let rust_app_3: TockApp = TockApp::Fixed(FixedApp { + installed: false, + idx: None, + compatible_addresses: vec![ + Some((0x40000, 0x20008000)), + Some((0x42000, 0x2000a000)), + Some((0x48000, 0x20010000)), + Some((0x80000, 0x20006000)), + Some((0x88000, 0x2000e000)), + ], + size: 0x2000, + }); + + let apps: Vec = vec![rust_app_1, rust_app_2, rust_app_3]; + + let reshuffled_apps = reshuffle_apps(settings, apps); + + let correct_config: Option> = Some(vec![ + Index { + installed: true, + idx: Some(0x0), + ram_address: Some(0x20008000), + address: 0x40000, + size: 0x2000, + }, + Index { + installed: true, + idx: Some(0x1), + ram_address: Some(0x2000a000), + address: 0x42000, + size: 0x2000, + }, + // padding + Index { + installed: false, + idx: None, + ram_address: None, + address: 0x44000, + size: 0x4000, + }, + Index { + installed: false, + idx: Some(0x2), + ram_address: Some(0x20010000), + address: 0x48000, + size: 0x2000, + }, + ]); + assert_eq!(reshuffled_apps, correct_config); +} + +#[test] +fn insert_c_app_between_rust_apps() { + let settings: &BoardSettings = &BoardSettings { + arch: Some("cortex-m4".to_string()), + start_address: 0x00040000, + page_size: 512, + ram_start_address: 0x20000000, + }; + + let rust_app_1: TockApp = TockApp::Fixed(FixedApp { + installed: true, + idx: None, + compatible_addresses: vec![Some((0x40000, 0x20008000))], + size: 0x2000, + }); + + let rust_app_2: TockApp = TockApp::Fixed(FixedApp { + installed: true, + idx: None, + compatible_addresses: vec![Some((0x42000, 0x2000a000))], + size: 0x2000, + }); + + let rust_app_3: TockApp = TockApp::Fixed(FixedApp { + installed: true, + idx: None, + compatible_addresses: vec![Some((0x48000, 0x20010000))], + size: 0x2000, + }); + + let c_app_1: TockApp = TockApp::Flexible(FlexibleApp { + installed: false, + idx: None, + size: 0x2000, + }); + + let apps: Vec = vec![rust_app_1, rust_app_2, rust_app_3, c_app_1]; + + let reshuffled_apps = reshuffle_apps(settings, apps); + + let correct_config: Option> = Some(vec![ + Index { + installed: true, + idx: Some(0x0), + ram_address: Some(0x20008000), + address: 0x40000, + size: 0x2000, + }, + Index { + installed: true, + idx: Some(0x1), + ram_address: Some(0x2000a000), + address: 0x42000, + size: 0x2000, + }, + Index { + installed: false, + idx: Some(0x3), + ram_address: None, + address: 0x44000, + size: 0x2000, + }, + // padding + Index { + installed: false, + idx: None, + ram_address: None, + address: 0x46000, + size: 0x2000, + }, + Index { + installed: true, + idx: Some(0x2), + ram_address: Some(0x20010000), + address: 0x48000, + size: 0x2000, + }, + ]); + assert_eq!(reshuffled_apps, correct_config); +} From 156db21ea238f11f0161022aa162f3d0dcc9bb89 Mon Sep 17 00:00:00 2001 From: Adrian Lungu Date: Tue, 14 Apr 2026 21:46:53 +0300 Subject: [PATCH 7/8] chore: switched to unit testing Signed-off-by: Adrian Lungu --- tockloader-lib/src/command_impl/install.rs | 2 +- .../src/command_impl/reshuffle_apps.rs | 276 +++++++++++++++++- tockloader-lib/src/tabs/tab.rs | 16 +- tockloader-lib/tests/reshuffle_apps.rs | 249 ---------------- 4 files changed, 271 insertions(+), 272 deletions(-) delete mode 100644 tockloader-lib/tests/reshuffle_apps.rs diff --git a/tockloader-lib/src/command_impl/install.rs b/tockloader-lib/src/command_impl/install.rs index 5737f19c..d3af1978 100644 --- a/tockloader-lib/src/command_impl/install.rs +++ b/tockloader-lib/src/command_impl/install.rs @@ -14,7 +14,7 @@ impl CommandInstall for TockloaderConnection { let app_attributes_list: Vec = self.list().await?; let mut tock_app_list = app_attributes_list .iter() - .map(|app| TockApp::from_app_attributes(app)) + .map(TockApp::from_app_attributes) .collect::>(); log::info!("tock apps len {:?}", tock_app_list.len()); diff --git a/tockloader-lib/src/command_impl/reshuffle_apps.rs b/tockloader-lib/src/command_impl/reshuffle_apps.rs index 839b15f6..b0fec8cf 100644 --- a/tockloader-lib/src/command_impl/reshuffle_apps.rs +++ b/tockloader-lib/src/command_impl/reshuffle_apps.rs @@ -96,7 +96,7 @@ impl TockApp { let header = parse_tbf_header(&binary[0..header_len as usize], tbf_version).expect("invalid header"); - if let Some(_) = header.get_fixed_address_flash() { + if header.get_fixed_address_flash().is_some() { // addr = align_down(addr as u64) as u32; // if addr < settings.start_address as u32 { // // this rust app should not be here @@ -481,29 +481,277 @@ pub fn create_pkt( ) -> Vec { let mut pkt: Vec = Vec::new(); for item in configuration.iter() { - if item.idx.is_none() { - // write padding binary - let mut buf = create_padding(item.size as u32); - pkt.append(&mut buf); - } else { + if let Some(idx) = item.idx { match &item.installed { - true => pkt.append(&mut app_binaries[item.idx.unwrap()]), + true => pkt.append(&mut app_binaries[idx]), false => { let mut arch: String = settings.arch.clone().unwrap(); // if ram is set, this is a rust app, we need to reconstruct the arch and then // read the binary from the tab - if item.ram_address.is_some() { - arch = format!( - "{}.0x{:08x}.0x{:08x}", - arch, - item.address, - item.ram_address.unwrap() - ); + if let Some(ram_address) = item.ram_address { + arch = format!("{}.0x{:08x}.0x{:08x}", arch, item.address, ram_address); } pkt.append(&mut tab.as_ref().unwrap().extract_binary(arch).unwrap()); } } + } else { + // write padding binary + let mut buf = create_padding(item.size as u32); + pkt.append(&mut buf); } } pkt } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn install_new_c_app() { + let settings: &BoardSettings = &BoardSettings { + arch: Some("cortex-m4".to_string()), + start_address: 0x00040000, + page_size: 512, + ram_start_address: 0x20000000, + }; + + let c_app_1: TockApp = TockApp::Flexible(FlexibleApp { + installed: true, + idx: None, + size: 0x2000, + }); + + let c_app_2: TockApp = TockApp::Flexible(FlexibleApp { + installed: true, + idx: None, + size: 0x2000, + }); + + let c_app_3: TockApp = TockApp::Flexible(FlexibleApp { + installed: false, + idx: None, + size: 0x2000, + }); + + let apps: Vec = vec![c_app_1, c_app_2, c_app_3]; + + let reshuffled_apps = reshuffle_apps(settings, apps); + + let correct_config: Option> = Some(vec![ + Index { + installed: true, + idx: Some(0x0), + ram_address: None, + address: 0x40000, + size: 0x2000, + }, + Index { + installed: true, + idx: Some(0x1), + ram_address: None, + address: 0x42000, + size: 0x2000, + }, + Index { + installed: false, + idx: Some(0x2), + ram_address: None, + address: 0x44000, + size: 0x2000, + }, + ]); + assert_eq!(reshuffled_apps, correct_config); + } + + #[test] + fn install_new_rust_app() { + let settings: &BoardSettings = &BoardSettings { + arch: Some("cortex-m4".to_string()), + start_address: 0x00040000, + page_size: 512, + ram_start_address: 0x20000000, + }; + + let rust_app: TockApp = TockApp::Fixed(FixedApp { + installed: false, + idx: None, + compatible_addresses: vec![ + Some((0x40000, 0x20008000)), + Some((0x42000, 0x2000a000)), + Some((0x48000, 0x20010000)), + Some((0x80000, 0x20006000)), + Some((0x88000, 0x2000e000)), + ], + size: 0x2000, + }); + + let apps: Vec = vec![rust_app]; + + let reshuffled_apps = reshuffle_apps(settings, apps); + + let correct_config: Option> = Some(vec![Index { + installed: false, + idx: Some(0x0), + ram_address: Some(0x20008000), + address: 0x40000, + size: 0x2000, + }]); + assert_eq!(reshuffled_apps, correct_config); + } + + #[test] + fn install_more_rust_apps() { + let settings: &BoardSettings = &BoardSettings { + arch: Some("cortex-m4".to_string()), + start_address: 0x00040000, + page_size: 512, + ram_start_address: 0x20000000, + }; + + let rust_app_1: TockApp = TockApp::Fixed(FixedApp { + installed: true, + idx: None, + compatible_addresses: vec![Some((0x40000, 0x20008000))], + size: 0x2000, + }); + + let rust_app_2: TockApp = TockApp::Fixed(FixedApp { + installed: true, + idx: None, + compatible_addresses: vec![Some((0x42000, 0x2000a000))], + size: 0x2000, + }); + + let rust_app_3: TockApp = TockApp::Fixed(FixedApp { + installed: false, + idx: None, + compatible_addresses: vec![ + Some((0x40000, 0x20008000)), + Some((0x42000, 0x2000a000)), + Some((0x48000, 0x20010000)), + Some((0x80000, 0x20006000)), + Some((0x88000, 0x2000e000)), + ], + size: 0x2000, + }); + + let apps: Vec = vec![rust_app_1, rust_app_2, rust_app_3]; + + let reshuffled_apps = reshuffle_apps(settings, apps); + + let correct_config: Option> = Some(vec![ + Index { + installed: true, + idx: Some(0x0), + ram_address: Some(0x20008000), + address: 0x40000, + size: 0x2000, + }, + Index { + installed: true, + idx: Some(0x1), + ram_address: Some(0x2000a000), + address: 0x42000, + size: 0x2000, + }, + // padding + Index { + installed: false, + idx: None, + ram_address: None, + address: 0x44000, + size: 0x4000, + }, + Index { + installed: false, + idx: Some(0x2), + ram_address: Some(0x20010000), + address: 0x48000, + size: 0x2000, + }, + ]); + assert_eq!(reshuffled_apps, correct_config); + } + + #[test] + fn insert_c_app_between_rust_apps() { + let settings: &BoardSettings = &BoardSettings { + arch: Some("cortex-m4".to_string()), + start_address: 0x00040000, + page_size: 512, + ram_start_address: 0x20000000, + }; + + let rust_app_1: TockApp = TockApp::Fixed(FixedApp { + installed: true, + idx: None, + compatible_addresses: vec![Some((0x40000, 0x20008000))], + size: 0x2000, + }); + + let rust_app_2: TockApp = TockApp::Fixed(FixedApp { + installed: true, + idx: None, + compatible_addresses: vec![Some((0x42000, 0x2000a000))], + size: 0x2000, + }); + + let rust_app_3: TockApp = TockApp::Fixed(FixedApp { + installed: true, + idx: None, + compatible_addresses: vec![Some((0x48000, 0x20010000))], + size: 0x2000, + }); + + let c_app_1: TockApp = TockApp::Flexible(FlexibleApp { + installed: false, + idx: None, + size: 0x2000, + }); + + let apps: Vec = vec![rust_app_1, rust_app_2, rust_app_3, c_app_1]; + + let reshuffled_apps = reshuffle_apps(settings, apps); + + let correct_config: Option> = Some(vec![ + Index { + installed: true, + idx: Some(0x0), + ram_address: Some(0x20008000), + address: 0x40000, + size: 0x2000, + }, + Index { + installed: true, + idx: Some(0x1), + ram_address: Some(0x2000a000), + address: 0x42000, + size: 0x2000, + }, + Index { + installed: false, + idx: Some(0x3), + ram_address: None, + address: 0x44000, + size: 0x2000, + }, + // padding + Index { + installed: false, + idx: None, + ram_address: None, + address: 0x46000, + size: 0x2000, + }, + Index { + installed: true, + idx: Some(0x2), + ram_address: Some(0x20010000), + address: 0x48000, + size: 0x2000, + }, + ]); + assert_eq!(reshuffled_apps, correct_config); + } +} diff --git a/tockloader-lib/src/tabs/tab.rs b/tockloader-lib/src/tabs/tab.rs index d11da539..5c90cade 100644 --- a/tockloader-lib/src/tabs/tab.rs +++ b/tockloader-lib/src/tabs/tab.rs @@ -93,15 +93,15 @@ impl Tab { let (arch, flash, ram) = Self::split_arch(file.filename.to_string()); // check if we have the same arch // check if flash and ram fit - if flash != 0 && ram != 0 { - if arch.starts_with(settings.arch.as_ref().unwrap()) - && flash >= settings.start_address - && ram >= settings.ram_start_address - { - log::info!("rust, pushed arch {arch}, flash {flash:#x}, ram {ram:#x}"); - compatible_tbfs.push(Some((flash, ram))); - } + if flash != 0 + && ram != 0 + && arch.starts_with(settings.arch.as_ref().unwrap()) + && flash >= settings.start_address + { + log::info!("rust, pushed arch {arch}, flash {flash:#x}, ram {ram:#x}"); + compatible_tbfs.push(Some((flash, ram))); } + // how about we don't do anything on else? // } else if arch.starts_with(settings.arch.as_ref().unwrap()) { // // this happens for C apps, we'll have diff --git a/tockloader-lib/tests/reshuffle_apps.rs b/tockloader-lib/tests/reshuffle_apps.rs deleted file mode 100644 index 82065940..00000000 --- a/tockloader-lib/tests/reshuffle_apps.rs +++ /dev/null @@ -1,249 +0,0 @@ -use tockloader_lib::{board_settings::BoardSettings, command_impl::reshuffle_apps::*}; - -#[test] -fn install_new_c_app() { - let settings: &BoardSettings = &BoardSettings { - arch: Some("cortex-m4".to_string()), - start_address: 0x00040000, - page_size: 512, - ram_start_address: 0x20000000, - }; - - let c_app_1: TockApp = TockApp::Flexible(FlexibleApp { - installed: true, - idx: None, - size: 0x2000, - }); - - let c_app_2: TockApp = TockApp::Flexible(FlexibleApp { - installed: true, - idx: None, - size: 0x2000, - }); - - let c_app_3: TockApp = TockApp::Flexible(FlexibleApp { - installed: false, - idx: None, - size: 0x2000, - }); - - let apps: Vec = vec![c_app_1, c_app_2, c_app_3]; - - let reshuffled_apps = reshuffle_apps(settings, apps); - - let correct_config: Option> = Some(vec![ - Index { - installed: true, - idx: Some(0x0), - ram_address: None, - address: 0x40000, - size: 0x2000, - }, - Index { - installed: true, - idx: Some(0x1), - ram_address: None, - address: 0x42000, - size: 0x2000, - }, - Index { - installed: false, - idx: Some(0x2), - ram_address: None, - address: 0x44000, - size: 0x2000, - }, - ]); - assert_eq!(reshuffled_apps, correct_config); -} - -#[test] -fn install_new_rust_app() { - let settings: &BoardSettings = &BoardSettings { - arch: Some("cortex-m4".to_string()), - start_address: 0x00040000, - page_size: 512, - ram_start_address: 0x20000000, - }; - - let rust_app: TockApp = TockApp::Fixed(FixedApp { - installed: false, - idx: None, - compatible_addresses: vec![ - Some((0x40000, 0x20008000)), - Some((0x42000, 0x2000a000)), - Some((0x48000, 0x20010000)), - Some((0x80000, 0x20006000)), - Some((0x88000, 0x2000e000)), - ], - size: 0x2000, - }); - - let apps: Vec = vec![rust_app]; - - let reshuffled_apps = reshuffle_apps(settings, apps); - - let correct_config: Option> = Some(vec![Index { - installed: false, - idx: Some(0x0), - ram_address: Some(0x20008000), - address: 0x40000, - size: 0x2000, - }]); - assert_eq!(reshuffled_apps, correct_config); -} - -#[test] -fn install_more_rust_apps() { - let settings: &BoardSettings = &BoardSettings { - arch: Some("cortex-m4".to_string()), - start_address: 0x00040000, - page_size: 512, - ram_start_address: 0x20000000, - }; - - let rust_app_1: TockApp = TockApp::Fixed(FixedApp { - installed: true, - idx: None, - compatible_addresses: vec![Some((0x40000, 0x20008000))], - size: 0x2000, - }); - - let rust_app_2: TockApp = TockApp::Fixed(FixedApp { - installed: true, - idx: None, - compatible_addresses: vec![Some((0x42000, 0x2000a000))], - size: 0x2000, - }); - - let rust_app_3: TockApp = TockApp::Fixed(FixedApp { - installed: false, - idx: None, - compatible_addresses: vec![ - Some((0x40000, 0x20008000)), - Some((0x42000, 0x2000a000)), - Some((0x48000, 0x20010000)), - Some((0x80000, 0x20006000)), - Some((0x88000, 0x2000e000)), - ], - size: 0x2000, - }); - - let apps: Vec = vec![rust_app_1, rust_app_2, rust_app_3]; - - let reshuffled_apps = reshuffle_apps(settings, apps); - - let correct_config: Option> = Some(vec![ - Index { - installed: true, - idx: Some(0x0), - ram_address: Some(0x20008000), - address: 0x40000, - size: 0x2000, - }, - Index { - installed: true, - idx: Some(0x1), - ram_address: Some(0x2000a000), - address: 0x42000, - size: 0x2000, - }, - // padding - Index { - installed: false, - idx: None, - ram_address: None, - address: 0x44000, - size: 0x4000, - }, - Index { - installed: false, - idx: Some(0x2), - ram_address: Some(0x20010000), - address: 0x48000, - size: 0x2000, - }, - ]); - assert_eq!(reshuffled_apps, correct_config); -} - -#[test] -fn insert_c_app_between_rust_apps() { - let settings: &BoardSettings = &BoardSettings { - arch: Some("cortex-m4".to_string()), - start_address: 0x00040000, - page_size: 512, - ram_start_address: 0x20000000, - }; - - let rust_app_1: TockApp = TockApp::Fixed(FixedApp { - installed: true, - idx: None, - compatible_addresses: vec![Some((0x40000, 0x20008000))], - size: 0x2000, - }); - - let rust_app_2: TockApp = TockApp::Fixed(FixedApp { - installed: true, - idx: None, - compatible_addresses: vec![Some((0x42000, 0x2000a000))], - size: 0x2000, - }); - - let rust_app_3: TockApp = TockApp::Fixed(FixedApp { - installed: true, - idx: None, - compatible_addresses: vec![Some((0x48000, 0x20010000))], - size: 0x2000, - }); - - let c_app_1: TockApp = TockApp::Flexible(FlexibleApp { - installed: false, - idx: None, - size: 0x2000, - }); - - let apps: Vec = vec![rust_app_1, rust_app_2, rust_app_3, c_app_1]; - - let reshuffled_apps = reshuffle_apps(settings, apps); - - let correct_config: Option> = Some(vec![ - Index { - installed: true, - idx: Some(0x0), - ram_address: Some(0x20008000), - address: 0x40000, - size: 0x2000, - }, - Index { - installed: true, - idx: Some(0x1), - ram_address: Some(0x2000a000), - address: 0x42000, - size: 0x2000, - }, - Index { - installed: false, - idx: Some(0x3), - ram_address: None, - address: 0x44000, - size: 0x2000, - }, - // padding - Index { - installed: false, - idx: None, - ram_address: None, - address: 0x46000, - size: 0x2000, - }, - Index { - installed: true, - idx: Some(0x2), - ram_address: Some(0x20010000), - address: 0x48000, - size: 0x2000, - }, - ]); - assert_eq!(reshuffled_apps, correct_config); -} From eacf53d4b2ffe72301ebbe3cfd0d5961b8b6fe20 Mon Sep 17 00:00:00 2001 From: Adrian Lungu Date: Fri, 17 Apr 2026 22:16:50 +0300 Subject: [PATCH 8/8] feat: uninstall apps Signed-off-by: Adrian Lungu --- tockloader-cli/src/cli.rs | 6 ++ tockloader-cli/src/main.rs | 60 +++++++++++++++- .../src/attributes/app_attributes.rs | 29 ++++++++ tockloader-lib/src/command_impl/install.rs | 3 +- tockloader-lib/src/command_impl/mod.rs | 1 + tockloader-lib/src/command_impl/uninstall.rs | 70 +++++++++++++++++++ tockloader-lib/src/lib.rs | 14 ++++ 7 files changed, 180 insertions(+), 3 deletions(-) create mode 100644 tockloader-lib/src/command_impl/uninstall.rs diff --git a/tockloader-cli/src/cli.rs b/tockloader-cli/src/cli.rs index 529a4bf7..611f0c3c 100644 --- a/tockloader-cli/src/cli.rs +++ b/tockloader-cli/src/cli.rs @@ -58,6 +58,12 @@ fn get_subcommands() -> Vec { .args(get_app_args()) .args(get_channel_args()) .arg_required_else_help(false), + Command::new("uninstall") + .about("Uninstall apps") + .arg(arg!(--"name" ).required(false)) + .args(get_app_args()) + .args(get_channel_args()) + .arg_required_else_help(false), ] } diff --git a/tockloader-cli/src/main.rs b/tockloader-cli/src/main.rs index eaee4323..5ea8e3ef 100644 --- a/tockloader-cli/src/main.rs +++ b/tockloader-cli/src/main.rs @@ -10,6 +10,7 @@ use anyhow::{Context, Result}; use clap::ArgMatches; use cli::make_cli; use known_boards::KnownBoardNames; +use tockloader_lib::attributes::app_attributes::AppOption; use tockloader_lib::board_settings::BoardSettings; use tockloader_lib::connection::{ Connection, ProbeRSConnection, ProbeTargetInfo, SerialConnection, SerialTargetInfo, @@ -19,7 +20,7 @@ use tockloader_lib::known_boards::KnownBoard; use tockloader_lib::tabs::tab::Tab; use tockloader_lib::{ list_debug_probes, list_serial_ports, CommandEraseApps, CommandInfo, CommandInstall, - CommandList, + CommandList, CommandUninstall, }; fn get_serial_target_info(user_options: &ArgMatches) -> SerialTargetInfo { @@ -240,6 +241,63 @@ async fn main() -> Result<()> { conn.erase_apps().await.context("Failed to erase apps.")?; } + Some(("uninstall", sub_matches)) => { + cli::validate(&mut cmd, sub_matches); + let mut conn = open_connection(sub_matches).await?; + let installed_apps = conn.list().await.context("Failed to list apps.")?; + let app_name = sub_matches.get_one::("name").map(String::as_str); + + if installed_apps.is_empty() { + println!("No apps installed"); + return Ok(()); + } + match app_name { + Some(app_name) => { + installed_apps + .iter() + .find(|iter| iter.tbf_header.get_package_name().unwrap_or("") == app_name) + .expect("Specified app is not installed"); + conn.uninstall_app(Some(app_name.to_string()), None) + .await + .context("Failed to uninstall app.")?; + } + None => loop { + let mut options: Vec = installed_apps + .iter() + .enumerate() + .map(|(i, app)| AppOption { index: i + 1, app }) + .collect(); + + // Delete all option + options.insert( + 0, + AppOption { + index: 0, + app: &installed_apps[0], + }, + ); + let selected = + inquire::Select::new("Which app do you want to uninstall?", options) + .prompt() + .context("No apps installed") + .unwrap(); + + if inquire::Select::new( + format!("You chose {selected}",).as_str(), + ["Cancel", "Confirm"].to_vec(), + ) + .prompt() + .unwrap() + == "Confirm" + { + conn.uninstall_app(None, Some(selected.index)) + .await + .context("Failed to uninstall app")?; + break; + } + }, + } + } _ => { println!("Could not run the provided subcommand."); _ = make_cli().print_help(); diff --git a/tockloader-lib/src/attributes/app_attributes.rs b/tockloader-lib/src/attributes/app_attributes.rs index 31686b3e..7abbe0f0 100644 --- a/tockloader-lib/src/attributes/app_attributes.rs +++ b/tockloader-lib/src/attributes/app_attributes.rs @@ -21,6 +21,35 @@ pub struct AppAttributes { pub tbf_footers: Vec, } +/// This structure is used for displaying installed apps in the CLI +#[derive(Debug)] +pub struct AppOption<'a> { + pub index: usize, + pub app: &'a AppAttributes, +} + +impl<'a> std::fmt::Display for AppOption<'a> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + if self.index == 0 { + write!(f, "Delete all") + } else { + write!( + f, + "{}. {} - start: {:#x}, size: {}, type: {}", + self.index, + self.app.tbf_header.get_package_name().unwrap_or(""), + self.app.address, + self.app.tbf_header.total_size(), + if self.app.tbf_header.get_fixed_address_flash().is_none() { + "C (flexible)" + } else { + "Rust (fixed)" + } + ) + } + } +} + /// This structure represents a footer of a Tock application. Currently, footers /// only contain credentials, which are used to verify the integrity of the /// application. diff --git a/tockloader-lib/src/command_impl/install.rs b/tockloader-lib/src/command_impl/install.rs index d3af1978..1be7dc6e 100644 --- a/tockloader-lib/src/command_impl/install.rs +++ b/tockloader-lib/src/command_impl/install.rs @@ -21,14 +21,13 @@ impl CommandInstall for TockloaderConnection { // obtain the binaries in a vector let mut app_binaries: Vec> = Vec::new(); - let mut address = settings.start_address; for app in app_attributes_list.iter() { + let address = app.address; app_binaries.push( self.read(address, app.tbf_header.total_size() as usize) .await .unwrap(), ); - address += app.tbf_header.total_size() as u64; } let app = TockApp::from_tab(&tab, &settings).unwrap(); diff --git a/tockloader-lib/src/command_impl/mod.rs b/tockloader-lib/src/command_impl/mod.rs index 22d488e1..5aaf7b81 100644 --- a/tockloader-lib/src/command_impl/mod.rs +++ b/tockloader-lib/src/command_impl/mod.rs @@ -6,3 +6,4 @@ pub mod list; pub mod probers; pub mod reshuffle_apps; pub mod serial; +pub mod uninstall; diff --git a/tockloader-lib/src/command_impl/uninstall.rs b/tockloader-lib/src/command_impl/uninstall.rs new file mode 100644 index 00000000..97e75e7a --- /dev/null +++ b/tockloader-lib/src/command_impl/uninstall.rs @@ -0,0 +1,70 @@ +use async_trait::async_trait; + +use crate::{ + attributes::app_attributes::AppAttributes, + command_impl::reshuffle_apps::{create_pkt, reshuffle_apps, TockApp}, + connection::{Connection, TockloaderConnection}, + errors::{InternalError, TockloaderError}, + CommandEraseApps, CommandList, CommandUninstall, IO, +}; + +#[async_trait] +impl CommandUninstall for TockloaderConnection { + async fn uninstall_app( + &mut self, + app_name: Option, + app_index: Option, + ) -> Result<(), TockloaderError> { + let settings = self.get_settings(); + + let mut app_attributes_list: Vec = self.list().await?; + + // Remove all apps with given name + if let Some(name) = app_name { + let _ = app_attributes_list + .retain(|app| app.tbf_header.get_package_name().unwrap_or("") != name); + } else if let Some(index) = app_index { + // Delete all apps, call erase + if index == 0 { + self.erase_apps().await?; + return Ok(()); + } + // Remove the selected index + app_attributes_list.remove(index - 1); + } else { + panic!("Called uninstall with wrong parameters!"); + } + + let tock_app_list = app_attributes_list + .iter() + .map(TockApp::from_app_attributes) + .collect::>(); + + // obtain the binaries in a vector + let mut app_binaries: Vec> = Vec::new(); + + for app in app_attributes_list.iter() { + let address = app.address; + app_binaries.push( + self.read(address, app.tbf_header.total_size() as usize) + .await + .unwrap(), + ); + } + + let configuration = + reshuffle_apps(&settings, tock_app_list).ok_or(TockloaderError::Internal( + InternalError::MisconfiguredBoardSettings("Can't fit new app".to_string()), + ))?; + + // create the pkt, this contains all the binaries in a vec + let mut pkt = create_pkt(configuration, app_binaries, None, &settings); + + pkt.append(&mut [0u8; 512].to_vec()); + + log::debug!("pkt len {}", pkt.len()); + // write the pkt + let _ = self.write(settings.start_address, &pkt).await?; + Ok(()) + } +} diff --git a/tockloader-lib/src/lib.rs b/tockloader-lib/src/lib.rs index fd023eef..d9f5c068 100644 --- a/tockloader-lib/src/lib.rs +++ b/tockloader-lib/src/lib.rs @@ -50,6 +50,20 @@ pub trait CommandInstall { async fn install_app(&mut self, tab_file: Tab) -> Result<(), TockloaderError>; } +#[async_trait] +pub trait CommandUninstall { + /// This function is used for uninstalling apps + /// - app_name is Some(value) if --name is used, otherwise it is None + /// - app_index is Some(value) if the app is chosen from the list, None otherwise + /// + /// There is no scenario in which both are Some(value) or None + async fn uninstall_app( + &mut self, + app_name: Option, + app_index: Option, + ) -> Result<(), TockloaderError>; +} + #[async_trait] pub trait CommandEraseApps { async fn erase_apps(&mut self) -> Result<(), TockloaderError>;