diff --git a/tbf-parser/src/types.rs b/tbf-parser/src/types.rs index 5f4dca45..07954f14 100644 --- a/tbf-parser/src/types.rs +++ b/tbf-parser/src/types.rs @@ -866,6 +866,7 @@ pub struct TbfHeaderV2 { // Clippy suggests we box TbfHeaderV2. We can't really do that, since // we are runnning under no_std, and I don't think it's that big of a issue. #[allow(clippy::large_enum_variant)] +#[derive(Clone)] pub enum TbfHeader { TbfHeaderV2(TbfHeaderV2), Padding(TbfHeaderV2Base), 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/Cargo.toml b/tockloader-lib/Cargo.toml index e89bc42e..ae42f367 100644 --- a/tockloader-lib/Cargo.toml +++ b/tockloader-lib/Cargo.toml @@ -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/attributes/app_attributes.rs b/tockloader-lib/src/attributes/app_attributes.rs index 58c0aa4b..654795ab 100644 --- a/tockloader-lib/src/attributes/app_attributes.rs +++ b/tockloader-lib/src/attributes/app_attributes.rs @@ -19,6 +19,7 @@ use crate::errors::{TockError, TockloaderError}; /// See also #[derive(Debug)] pub struct AppAttributes { + pub address: u64, pub tbf_header: TbfHeader, pub tbf_footers: Vec, } @@ -26,7 +27,7 @@ pub struct AppAttributes { /// This structure represents a footer of a Tock application. Currently, footers /// only contain credentials, which are used to verify the integrity of the /// application. -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct TbfFooter { pub credentials: TbfFooterV2Credentials, pub size: u32, @@ -41,8 +42,13 @@ impl TbfFooter { // TODO(george-cosma): Could take advantages of the trait rework impl AppAttributes { - pub(crate) fn new(header_data: TbfHeader, footers_data: Vec) -> AppAttributes { + pub(crate) fn new( + address: u64, + header_data: TbfHeader, + footers_data: Vec, + ) -> AppAttributes { AppAttributes { + address, tbf_header: header_data, tbf_footers: footers_data, } @@ -117,13 +123,10 @@ impl AppAttributes { // crash the process. let binary_end_offset = header.get_binary_end(); - match &header { - TbfHeader::TbfHeaderV2(_hd) => {} - _ => { - appaddr += total_size as u64; - continue; - } - }; + if !header.is_app() { + appaddr += total_size as u64; + continue; + } let mut footers: Vec = vec![]; let total_footers_size = total_size - binary_end_offset; @@ -137,9 +140,9 @@ impl AppAttributes { // binary_end_offset`) , even if we overread. let mut appfooter = vec![0u8; (total_footers_size - (footer_offset - binary_end_offset)) as usize]; - + // log::info!("footer init {:?}", appfooter); board_core.read(appaddr + footer_offset as u64, &mut appfooter)?; - + // log::info!("footer read {:?}", appfooter); let footer_info = parse_tbf_footer(&appfooter).map_err(TockError::InvalidAppTbfHeader)?; @@ -150,7 +153,7 @@ impl AppAttributes { footer_offset += footer_info.1 + 4; } - let details: AppAttributes = AppAttributes::new(header, footers); + let details: AppAttributes = AppAttributes::new(appaddr, header, footers); apps_details.insert(apps_counter, details); apps_counter += 1; @@ -240,15 +243,13 @@ impl AppAttributes { 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; - } - }; + if !header.is_app() { + appaddr += total_size as u64; + continue; + } let mut footers: Vec = vec![]; let total_footers_size = total_size - binary_end_offset; @@ -289,7 +290,7 @@ impl AppAttributes { footer_offset += footer_info.1 + 4; } - let details: AppAttributes = AppAttributes::new(header, footers); + let details: AppAttributes = AppAttributes::new(appaddr, header, footers); apps_details.insert(apps_counter, details); apps_counter += 1; diff --git a/tockloader-lib/src/board_settings.rs b/tockloader-lib/src/board_settings.rs index d7f67ab8..22be548b 100644 --- a/tockloader-lib/src/board_settings.rs +++ b/tockloader-lib/src/board_settings.rs @@ -1,6 +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 @@ -10,6 +13,8 @@ impl Default for BoardSettings { Self { arch: None, start_address: 0x30000, + page_size: 512, + ram_start_address: 0x20000000, } } } diff --git a/tockloader-lib/src/bootloader_serial.rs b/tockloader-lib/src/bootloader_serial.rs index 48107ec1..8893b171 100644 --- a/tockloader-lib/src/bootloader_serial.rs +++ b/tockloader-lib/src/bootloader_serial.rs @@ -5,7 +5,7 @@ // The "X" commands are for external flash use crate::errors::{self, InternalError, TockError}; -use bytes::BytesMut; +use bytes::{BufMut, BytesMut}; use errors::TockloaderError; use std::time::Duration; use tokio::io::{AsyncReadExt, AsyncWriteExt}; @@ -17,7 +17,7 @@ pub const SYNC_MESSAGE: [u8; 3] = [0x00, 0xFC, 0x05]; // "This was chosen as it is infrequent in .bin files" - immesys pub const ESCAPE_CHAR: u8 = 0xFC; -pub const DEFAULT_TIMEOUT: Duration = Duration::from_millis(500); +pub const DEFAULT_TIMEOUT: Duration = Duration::from_millis(5000); #[allow(dead_code)] pub enum Command { @@ -217,7 +217,7 @@ pub async fn issue_command( } if response_len != 0 { - let input = read_bytes(port, response_len, DEFAULT_TIMEOUT).await?; + let mut input = read_bytes(port, response_len, DEFAULT_TIMEOUT).await?; let mut result = Vec::with_capacity(input.len()); // De-escape and add array of read in the bytes @@ -227,6 +227,7 @@ pub async fn issue_command( while i < input.len() { if i + 1 < input.len() && input[i] == ESCAPE_CHAR && input[i + 1] == ESCAPE_CHAR { // Found consecutive ESCAPE_CHAR bytes, add only one + input.put(read_bytes(port, 1, DEFAULT_TIMEOUT).await?); result.push(ESCAPE_CHAR); i += 2; // Skip both bytes } else { 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..db676a20 --- /dev/null +++ b/tockloader-lib/src/command_impl/erase_apps.rs @@ -0,0 +1,13 @@ +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..2587190b --- /dev/null +++ b/tockloader-lib/src/command_impl/install.rs @@ -0,0 +1,49 @@ +use async_trait::async_trait; + +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::TockloaderError; +use crate::tabs::tab::Tab; +use crate::{CommandInstall, CommandList, IO}; + +#[async_trait] +impl CommandInstall for TockloaderConnection { + async fn install_app(&mut self, tab: Tab) -> Result<(), TockloaderError> { + let settings = self.get_settings(); + let app_attributes_list: Vec = self.list().await.unwrap(); + let mut 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(); + + 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 mut app = TockApp::from_tab(&tab, &settings).unwrap(); + + app.replace_idx(tock_app_list.len()); + tock_app_list.push(app.clone()); + + app_binaries.push(tab.extract_binary(settings.arch.clone().unwrap()).unwrap()); + + let configuration = reshuffle_apps(&settings, tock_app_list).unwrap(); + + // create the pkt, this contains all the binaries in a vec + let pkt = create_pkt(configuration, app_binaries); + + // write the pkt + let _ = self.write(settings.start_address, &pkt).await; + Ok(()) + } +} 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..22d488e1 100644 --- a/tockloader-lib/src/command_impl/mod.rs +++ b/tockloader-lib/src/command_impl/mod.rs @@ -1,3 +1,8 @@ +pub mod erase_apps; pub mod generalized; +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/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..046f145a --- /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 settings = self.get_settings(); + 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) + } + + 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/reshuffle_apps.rs b/tockloader-lib/src/command_impl/reshuffle_apps.rs new file mode 100644 index 00000000..cce5db4a --- /dev/null +++ b/tockloader-lib/src/command_impl/reshuffle_apps.rs @@ -0,0 +1,327 @@ +use itertools::Itertools; +use log::warn; + +use crate::attributes::app_attributes::AppAttributes; +use crate::board_settings::BoardSettings; +use crate::tabs::tab::{Tab, TbfFile}; +use tbf_parser::parse::{parse_tbf_header, parse_tbf_header_lengths}; + +const ALIGNMENT: u64 = 1024; + +#[derive(Clone)] +pub enum TockApp { + Flexible(FlexibleApp), + Fixed(FixedApp), +} + +#[derive(Clone)] +pub struct FlexibleApp { + idx: Option, + size: u64, +} + +#[derive(Clone)] +pub struct FixedApp { + idx: Option, + compatible_binaries: Vec<(Vec, u64, u64)>, + 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, + settings: &BoardSettings, + ) -> TockApp { + if let Some(address) = app_attributes.tbf_header.get_fixed_address_flash() { + TockApp::Fixed(FixedApp { + idx: None, + compatible_binaries: vec![vec![0u8], address, settings.ram_start_address], // (adi): change this when tbf selector gets merged + size: app_attributes.tbf_header.total_size() as u64, + }) + } else { + TockApp::Flexible(FlexibleApp { + 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(addr) = header.get_fixed_address_flash() { + 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 { + idx: None, + candidate_addresses: vec![address], // (adi): change this when tbf selector gets merged + size: total_size as u64, + })) + } else { + Some(TockApp::Flexible(FlexibleApp { + idx: None, + size: total_size as u64, + })) + } + } +} + +impl FixedApp { + fn as_index(&self, install_address: u64) -> Index { + Index { + idx: self.idx, + address: install_address, + size: self.size, + } + } +} + +impl FlexibleApp { + fn as_index(&self, install_address: u64) -> Index { + Index { + idx: self.idx, + address: install_address, + size: self.size, + } + } +} + +#[derive(Debug, Clone)] +pub struct Index { + idx: 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."); + } + } + + 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), + } + } + + for app in &mut rust_apps { + if app.candidate_addresses.is_empty() { + warn!("Can not reorder apps since fixed application has no candidate addresses!"); + return None; + } + + // TODO(eva-cosma): Remove this requirement + + // For now this algorithm only supports pre-chosen addresses for fixed apps. + // We will keep only the first address around. + if app.candidate_addresses.len() > 1 { + let first = app.candidate_addresses[0]; + app.candidate_addresses.clear(); + app.candidate_addresses.push(first); + } + } + + // this is necessary. If a rust app is already installed, for example: at 0x48000 + // and we want to install another one at 0x40000, reorder them first + rust_apps.sort_by_key(|app| app.candidate_addresses[0]); + + // 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(); + + 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 + insert_c = c_apps[order[permutation_index]].size + <= rust_apps[rust_index].candidate_addresses[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() { + // 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 { + if rust_apps[rust_index].candidate_addresses[0] < start_address { + // the program wants to insert a rust app where another rust app already exists + warn!( + "Can't insert the rust app, space is already occupied by another rust app" + ); + return None; + } + // rust app needs to be inserted at a fixed address, pad until there + rust_apps[rust_index].candidate_addresses[0] - start_address + }; + + if needed_padding > 0 { + // insert a padding + total_padding += needed_padding as usize; + reordered_apps.push(Index { + idx: 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(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(rust_apps[rust_index].candidate_addresses[0]); + 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; + } + } + + 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>) -> 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 { + pkt.append(&mut app_binaries[item.idx.unwrap()]); + } + } + pkt +} 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 c9ba88c7..00000000 --- a/tockloader-lib/src/command_impl/serial/install.rs +++ /dev/null @@ -1,239 +0,0 @@ -use async_trait::async_trait; - -use crate::board_settings::BoardSettings; -use crate::connection::SerialConnection; -use crate::errors::TockloaderError; -use crate::tabs::tab::Tab; -use crate::CommandInstall; - -#[async_trait] -impl CommandInstall for SerialConnection { - async fn install_app( - &mut self, - _settings: &BoardSettings, - _tab_file: Tab, - ) -> Result<(), TockloaderError> { - todo!() - } -} - -// pub async fn install_app( -// choice: Connection, -// core_index: Option<&usize>, -// tab_file: Tab, -// ) -> Result<(), TockloaderError> { -// match choice { -// Connection::ProbeRS(mut session) => { -// // *snip* -// } -// Connection::Serial(mut port) => { -// let response = ping_bootloader_and_wait_for_response(&mut port).await?; -// -// if response as u8 != Response::Pong as u8 { -// tokio::time::sleep(Duration::from_millis(100)).await; -// let _ = ping_bootloader_and_wait_for_response(&mut port).await?; -// } - -// let system_attributes = -// SystemAttributes::read_system_attributes_serial(&mut port).await?; - -// let board = system_attributes -// .board -// .ok_or("No board name found.".to_owned()); -// let kernel_version = system_attributes -// .kernel_version -// .ok_or("No kernel version found.".to_owned()); - -// match board { -// Ok(board) => { -// // Verify if the specified app is compatible with board -// // TODO(Micu Ana): Replace the print with log messages -// if tab_file.is_compatible_with_board(&board) { -// println!("Specified tab is compatible with board."); -// } else { -// panic!("Specified tab is not compatible with board."); -// } -// } -// Err(e) => { -// return Err(TockloaderError::MisconfiguredBoard(e)); -// } -// } - -// match kernel_version { -// Ok(kernel_version) => { -// // Verify if the specified app is compatible with kernel version -// // TODO(Micu Ana): Replace the prints with log messages -// if tab_file.is_compatible_with_kernel_verison(kernel_version as u32) { -// println!("Specified tab is compatible with your kernel version."); -// } else { -// println!("Specified tab is not compatible with your kernel version."); -// } -// } -// Err(e) => { -// return Err(TockloaderError::MisconfiguredBoard(e)); -// } -// } - -// let mut address = -// system_attributes -// .appaddr -// .ok_or(TockloaderError::MisconfiguredBoard( -// "No start address found.".to_owned(), -// ))?; -// 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(); -// for i in length { -// pkt.push(i); -// } - -// let (_, message) = issue_command( -// &mut port, -// Command::ReadRange, -// pkt, -// true, -// 200, -// Response::ReadRange, -// ) -// .await?; - -// 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()); - -// match arch { -// Ok(arch) => { -// let binary = tab_file.extract_binary(&arch.clone()); - -// match binary { -// Ok(mut binary) => { -// 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 needs_padding = binary.len() % page_size != 0; - -// if needs_padding { -// let remaining = page_size - (binary.len() % page_size); -// for _i in 0..remaining { -// binary.push(0xFF); -// } -// } - -// let binary_len = binary.len(); - -// // Get indices of pages that have valid data to write -// let mut valid_pages: Vec = Vec::new(); -// for i in 0..(binary_len / page_size) { -// for b in binary[(i * page_size)..((i + 1) * page_size)] -// .iter() -// .copied() -// { -// if b != 0 { -// valid_pages.push(i as u8); -// 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..(binary_len / page_size) { -// valid_pages.push(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 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) < (binary_len / page_size) as u8 -// { -// ending_pages.push(i + 1); -// } -// } - -// for i in ending_pages { -// valid_pages.push(i); -// } - -// 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 -// for b in binary -// [(i as usize * page_size)..((i + 1) as usize * page_size)] -// .iter() -// .copied() -// { -// pkt.push(b); -// } - -// // Write to bootloader -// let (_, _) = issue_command( -// &mut port, -// 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(); - -// let _ = issue_command( -// &mut port, -// Command::ErasePage, -// pkt, -// true, -// 0, -// Response::OK, -// ) -// .await?; -// } -// Err(e) => { -// return Err(e); -// } -// } -// Ok(()) -// } -// Err(e) => Err(TockloaderError::MisconfiguredBoard(e)), -// } -// } -// } -// } 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..618adfad --- /dev/null +++ b/tockloader-lib/src/command_impl/serial/io.rs @@ -0,0 +1,98 @@ +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(); + let length = (size as u16).to_le_bytes().to_vec(); + for i in length { + pkt.push(i); + } + 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 settings = self.get_settings(); + let stream = self.stream.as_mut().expect("Board must be open"); + let mut binary = pkt.to_vec(); + + if !binary.len().is_multiple_of(settings.page_size as usize) { + binary.extend(vec![ + 0u8; + settings.page_size as usize + - (binary.len() % settings.page_size as usize) + ]); + } + + for page_number in 0..(binary.len() / settings.page_size as usize) { + let mut pkt = (address as u32 + page_number as u32 * settings.page_size as u32) + .to_le_bytes() + .to_vec(); + pkt.append( + &mut binary[(page_number * settings.page_size as usize) + ..((page_number + 1) * settings.page_size as usize)] + .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 settings = self.get_settings(); + 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 + } + + 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..5ae56c82 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, + pub 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.clone() + } } 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, + pub 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.clone() + } } /// 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..009992de 100644 --- a/tockloader-lib/src/known_boards.rs +++ b/tockloader-lib/src/known_boards.rs @@ -25,6 +25,8 @@ impl KnownBoard for NucleoF4 { BoardSettings { arch: Some("cortex-m4".to_string()), start_address: 0x08040000, + page_size: 512, + ram_start_address: 0x20000000, } } } @@ -47,6 +49,8 @@ impl KnownBoard for MicrobitV2 { BoardSettings { arch: Some("cortex-m4".to_string()), start_address: 0x00040000, + page_size: 512, + ram_start_address: 0x20000000, } } } 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; } diff --git a/tockloader-lib/src/tabs/tab.rs b/tockloader-lib/src/tabs/tab.rs index fe69d54a..e5729586 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,6 +76,58 @@ impl Tab { } } + /// 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, u64, u64)>, TockloaderError> { + // save the file + // also save flash start and ram start for comparing easily later + let mut compatible_tbfs: Vec<(Vec, u64, u64)> = Vec::new(); + for file in &self.tbf_files { + 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 + { + compatible_tbfs.push((file.data.clone(), flash, ram)); + } + } 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((file.data.clone(), flash, ram)); + } + } + 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: &str) -> Result, TockloaderError> { for file in &self.tbf_files { if file.filename.starts_with(arch) {