From 82eee80f636044f81406c7aefaa6c4bbfaf8afac Mon Sep 17 00:00:00 2001 From: micuami <150240452+micuami@users.noreply.github.com> Date: Fri, 13 Sep 2024 11:10:53 +0300 Subject: [PATCH] feat: implement list and info with bootloader (#33) Co-authored-by: Micu Ana --- tockloader-cli/src/main.rs | 94 ++++--- .../src/attributes/app_attributes.rs | 112 +++++++- .../src/attributes/system_attributes.rs | 127 ++++++++- tockloader-lib/src/bootloader_serial.rs | 229 ++++++++++++++++ tockloader-lib/src/lib.rs | 245 +++++++++++++++--- 5 files changed, 730 insertions(+), 77 deletions(-) create mode 100644 tockloader-lib/src/bootloader_serial.rs diff --git a/tockloader-cli/src/main.rs b/tockloader-cli/src/main.rs index a283917..3d3a7f1 100644 --- a/tockloader-cli/src/main.rs +++ b/tockloader-cli/src/main.rs @@ -12,8 +12,7 @@ use errors::TockloaderError; use inquire::Select; use tockloader_lib::{ connection::{Connection, ConnectionInfo}, - info_probe, install_app_probe_rs, install_app_serial, list_debug_probes, list_probe, - list_serial_ports, + info, install_app, list, list_debug_probes, list_serial_ports, tabs::tab::Tab, }; @@ -43,38 +42,71 @@ async fn run() -> Result<(), TockloaderError> { }; } Some(("list", sub_matches)) => { - // TODO(george-cosma):inspect-err - // TODO(Micu Ana): Add error handling - let ans = - Select::new("Which debug probe do you want to use?", list_debug_probes()).prompt(); - // Open connection - let conn = Connection::open( - tockloader_lib::connection::ConnectionInfo::ProbeInfo(ans.unwrap()), - Some(sub_matches.get_one::("chip").unwrap().to_string()), - ); + if *sub_matches.get_one::("serial").unwrap() { + let serial_ports = list_serial_ports(); + // Let the user choose the port that will be used + let mut port_names = Vec::new(); + for port in serial_ports { + port_names.push(port.port_name); + } + // TODO(Micu Ana): Add error handling + let ans = Select::new("Which serial port do you want to use?", port_names) + .prompt() + .unwrap(); + // Open connection + let conn = Connection::open(ConnectionInfo::from(ans), None); + // Install app + let mut apps_details = list(conn.unwrap(), None).await.unwrap(); + print_list(&mut apps_details).await; + } else { + // TODO(george-cosma):inspect-err + // TODO(Micu Ana): Add error handling + let ans = Select::new("Which debug probe do you want to use?", list_debug_probes()) + .prompt(); + // Open connection + let conn = Connection::open( + tockloader_lib::connection::ConnectionInfo::ProbeInfo(ans.unwrap()), + Some(sub_matches.get_one::("chip").unwrap().to_string()), + ); - let mut apps_details = - list_probe(conn.unwrap(), sub_matches.get_one::("core").unwrap()) + let mut apps_details = list(conn.unwrap(), sub_matches.get_one::("core")) .await .unwrap(); - print_list(&mut apps_details).await; + print_list(&mut apps_details).await; + } } Some(("info", sub_matches)) => { - // TODO(Micu Ana): Add error handling - let ans = - Select::new("Which debug probe do you want to use?", list_debug_probes()).prompt(); - // Open connection - let conn = Connection::open( - tockloader_lib::connection::ConnectionInfo::ProbeInfo(ans.unwrap()), - Some(sub_matches.get_one::("chip").unwrap().to_string()), - ); + if *sub_matches.get_one::("serial").unwrap() { + let serial_ports = list_serial_ports(); + // Let the user choose the port that will be used + let mut port_names = Vec::new(); + for port in serial_ports { + port_names.push(port.port_name); + } + // TODO(Micu Ana): Add error handling + let ans = Select::new("Which serial port do you want to use?", port_names) + .prompt() + .unwrap(); + // Open connection + let conn = Connection::open(ConnectionInfo::from(ans), None); + let mut attributes = info(conn.unwrap(), None).await.unwrap(); + print_info(&mut attributes.apps, &mut attributes.system).await; + } else { + // TODO(Micu Ana): Add error handling + let ans = Select::new("Which debug probe do you want to use?", list_debug_probes()) + .prompt(); + // Open connection + let conn = Connection::open( + tockloader_lib::connection::ConnectionInfo::ProbeInfo(ans.unwrap()), + Some(sub_matches.get_one::("chip").unwrap().to_string()), + ); - let mut attributes = - info_probe(conn.unwrap(), sub_matches.get_one::("core").unwrap()) + let mut attributes = info(conn.unwrap(), sub_matches.get_one::("core")) .await .unwrap(); - print_info(&mut attributes.apps, &mut attributes.system).await; + print_info(&mut attributes.apps, &mut attributes.system).await; + } } Some(("install", sub_matches)) => { let tab_file = @@ -94,13 +126,7 @@ async fn run() -> Result<(), TockloaderError> { // Open connection let conn = Connection::open(ConnectionInfo::from(ans), None); // Install app - install_app_serial( - conn.unwrap(), - sub_matches.get_one::("board").unwrap(), - tab_file, - ) - .await - .unwrap(); + install_app(conn.unwrap(), None, tab_file).await.unwrap(); } else { // TODO(Micu Ana): Add error handling let ans = Select::new("Which debug probe do you want to use?", list_debug_probes()) @@ -111,9 +137,9 @@ async fn run() -> Result<(), TockloaderError> { Some(sub_matches.get_one::("chip").unwrap().to_string()), ); // Install app - install_app_probe_rs( + install_app( conn.unwrap(), - sub_matches.get_one::("core").unwrap(), + sub_matches.get_one::("core"), tab_file, ) .await diff --git a/tockloader-lib/src/attributes/app_attributes.rs b/tockloader-lib/src/attributes/app_attributes.rs index f1bc820..b75bcb1 100644 --- a/tockloader-lib/src/attributes/app_attributes.rs +++ b/tockloader-lib/src/attributes/app_attributes.rs @@ -9,6 +9,9 @@ use tbf_parser::{ parse::{parse_tbf_footer, parse_tbf_header, parse_tbf_header_lengths}, types::{TbfFooterV2Credentials, TbfHeader}, }; +use tokio_serial::SerialStream; + +use crate::bootloader_serial::{issue_command, Command, Response}; #[derive(Debug)] pub struct AppAttributes { @@ -23,7 +26,7 @@ pub struct TbfFooter { } impl TbfFooter { - fn new(credentials: TbfFooterV2Credentials, size: u32) -> TbfFooter { + pub fn new(credentials: TbfFooterV2Credentials, size: u32) -> TbfFooter { TbfFooter { credentials, size } } } @@ -37,7 +40,7 @@ impl AppAttributes { } // TODO: Document this function - pub(crate) fn read_apps_data(board_core: &mut Core, addr: u64) -> Vec { + pub(crate) fn read_apps_data_probe(board_core: &mut Core, addr: u64) -> Vec { let mut appaddr: u64 = addr; let mut apps_counter = 0; let mut apps_details: Vec = vec![]; @@ -110,4 +113,109 @@ impl AppAttributes { } apps_details } + + // TODO: Document this function + pub(crate) async fn read_apps_data_serial( + port: &mut SerialStream, + addr: u64, + ) -> Vec { + let mut appaddr: u64 = addr; + let mut apps_counter = 0; + let mut apps_details: Vec = vec![]; + + loop { + 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); + } + + let (_, appdata) = + issue_command(port, Command::ReadRange, pkt, true, 8, Response::ReadRange) + .await + .unwrap(); + + let tbf_version: u16; + let header_size: u16; + let total_size: u32; + + match parse_tbf_header_lengths(&appdata.try_into().unwrap()) { + Ok(data) => { + tbf_version = data.0; + header_size = data.1; + total_size = data.2; + } + _ => break, + }; + + 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); + } + + let (_, header_data) = issue_command( + port, + Command::ReadRange, + pkt, + true, + header_size.into(), + Response::ReadRange, + ) + .await + .unwrap(); + + let header: TbfHeader = parse_tbf_header(&header_data, tbf_version) + .unwrap_or_else(|e| panic!("Error found while getting tbf header data: {:?}", e)); + + let binary_end_offset = header.get_binary_end(); + + 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; + + loop { + 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); + } + + let (_, appfooter) = issue_command( + port, + Command::ReadRange, + pkt, + true, + (total_footers_size - (footer_offset - binary_end_offset)) + .try_into() + .unwrap(), + Response::ReadRange, + ) + .await + .unwrap(); + + let footer_info: (TbfFooterV2Credentials, u32) = parse_tbf_footer(&appfooter) + .unwrap_or_else(|e| panic!("Paniced while obtaining footer data: {:?}", e)); + + footers.insert(footer_number, TbfFooter::new(footer_info.0, footer_info.1)); + + footer_number += 1; + footer_offset += footer_info.1 + 4; + + if footer_offset == total_size { + break; + } + } + + let details: AppAttributes = AppAttributes::new(header, footers); + + apps_details.insert(apps_counter, details); + apps_counter += 1; + appaddr += total_size as u64; + } + apps_details + } } diff --git a/tockloader-lib/src/attributes/system_attributes.rs b/tockloader-lib/src/attributes/system_attributes.rs index 05abad4..1fa61ed 100644 --- a/tockloader-lib/src/attributes/system_attributes.rs +++ b/tockloader-lib/src/attributes/system_attributes.rs @@ -4,6 +4,9 @@ use byteorder::{ByteOrder, LittleEndian}; use probe_rs::{Core, MemoryInterface}; +use tokio_serial::SerialStream; + +use crate::bootloader_serial::{issue_command, Command, Response}; use super::decode::{bytes_to_string, decode_attribute}; @@ -40,7 +43,7 @@ impl SystemAttributes { } // TODO: explain what is happening here - pub(crate) fn read_system_attributes(board_core: &mut Core) -> Self { + pub(crate) fn read_system_attributes_probe(board_core: &mut Core) -> Self { let mut result = SystemAttributes::new(); let address = 0x600; let mut buf = [0u8; 64 * 16]; @@ -129,4 +132,126 @@ impl SystemAttributes { result } + + // TODO: explain what is happening here + pub(crate) async fn read_system_attributes_serial(port: &mut SerialStream) -> Self { + let mut result = SystemAttributes::new(); + + let mut pkt = (0x600_u32).to_le_bytes().to_vec(); + let length = (1024_u16).to_le_bytes().to_vec(); + for i in length { + pkt.push(i); + } + + let (_, buf) = issue_command( + port, + Command::ReadRange, + pkt, + true, + 64 * 16, + Response::ReadRange, + ) + .await + .unwrap(); + + let mut data = buf.chunks(64); + + for index_data in 0..data.len() { + let step = match data.next() { + Some(data) => data, + None => break, + }; + + let step_option = decode_attribute(step); + + if step_option.is_none() { + continue; + } + + let decoded_attributes = step_option.unwrap(); + + match index_data { + 0 => { + result.board = Some(decoded_attributes.value.to_string()); + } + 1 => { + result.arch = Some(decoded_attributes.value.to_string()); + } + 2 => { + result.appaddr = Some( + u64::from_str_radix( + decoded_attributes + .value + .to_string() + .trim_start_matches("0x"), + 16, + ) + .unwrap(), + ); + } + 3 => { + result.boothash = Some(decoded_attributes.value.to_string()); + } + _ => panic!("Board data not found!"), + } + } + + 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); + } + + let (_, buf) = issue_command(port, Command::ReadRange, pkt, true, 8, Response::ReadRange) + .await + .unwrap(); + + let decoder = utf8_decode::Decoder::new(buf.iter().cloned()); + + let mut string = String::new(); + for n in decoder { + string.push(n.expect("Error decoding bootloader version")); + } + + let string = string.trim_matches(char::from(0)); + + result.bootloader_version = Some(string.to_owned()); + + let mut pkt = ((result.appaddr.unwrap() - 100) as u32) + .to_le_bytes() + .to_vec(); + let length = (100_u16).to_le_bytes().to_vec(); + for i in length { + pkt.push(i); + } + + let (_, kernel_attr_binary) = issue_command( + port, + Command::ReadRange, + pkt, + true, + 100, + Response::ReadRange, + ) + .await + .unwrap(); + + 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); + + result + } } diff --git a/tockloader-lib/src/bootloader_serial.rs b/tockloader-lib/src/bootloader_serial.rs new file mode 100644 index 0000000..c9d18ab --- /dev/null +++ b/tockloader-lib/src/bootloader_serial.rs @@ -0,0 +1,229 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright OXIDOS AUTOMOTIVE 2024. + +// The "X" commands are for external flash + +use crate::errors; +use bytes::BytesMut; +use errors::TockloaderError; +use std::time::Duration; +use tokio::io::{AsyncReadExt, AsyncWriteExt}; +use tokio_serial::{SerialPort, SerialStream}; + +// Tell the bootloader to reset its buffer to handle a new command +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; + +#[allow(dead_code)] +pub enum Command { + // Commands from this tool to the bootloader + Ping = 0x01, + Info = 0x03, + ID = 0x04, + Reset = 0x05, + ErasePage = 0x06, + WritePage = 0x07, + XEBlock = 0x08, + XWPage = 0x09, + Crcx = 0x10, + ReadRange = 0x11, + XRRange = 0x12, + SetAttribute = 0x13, + GetAttribute = 0x14, + CRCInternalFlash = 0x15, + Crcef = 0x16, + XEPage = 0x17, + XFinit = 0x18, + ClkOut = 0x19, + WUser = 0x20, + ChangeBaudRate = 0x21, + Exit = 0x22, + SetStartAddress = 0x23, +} + +#[derive(Clone, Debug)] +pub enum Response { + // Responses from the bootloader + Overflow = 0x10, + Pong = 0x11, + BadAddr = 0x12, + IntError = 0x13, + BadArgs = 0x14, + OK = 0x15, + Unknown = 0x16, + XFTimeout = 0x17, + Xfepe = 0x18, + Crcrx = 0x19, + ReadRange = 0x20, + XRRange = 0x21, + GetAttribute = 0x22, + CRCInternalFlash = 0x23, + Crcxf = 0x24, + Info = 0x25, + ChangeBaudFail = 0x26, + BadResp, +} + +impl From for Response { + fn from(value: u8) -> Self { + match value { + 0x10 => Response::Overflow, + 0x11 => Response::Pong, + 0x12 => Response::BadAddr, + 0x13 => Response::IntError, + 0x14 => Response::BadArgs, + 0x15 => Response::OK, + 0x16 => Response::Unknown, + 0x17 => Response::XFTimeout, + 0x18 => Response::Xfepe, + 0x19 => Response::Crcrx, + 0x20 => Response::ReadRange, + 0x21 => Response::XRRange, + 0x22 => Response::GetAttribute, + 0x23 => Response::CRCInternalFlash, + 0x24 => Response::Crcxf, + 0x25 => Response::Info, + 0x26 => Response::ChangeBaudFail, + + // This error handling is temmporary + //TODO(Micu Ana): Add error handling + _ => Response::BadResp, + } + } +} + +#[allow(dead_code)] +pub async fn toggle_bootloader_entry_dtr_rts(port: &mut SerialStream) { + port.write_data_terminal_ready(true).unwrap(); + port.write_request_to_send(true).unwrap(); + tokio::time::sleep(Duration::from_millis(100)).await; + port.write_data_terminal_ready(false).unwrap(); + tokio::time::sleep(Duration::from_millis(500)).await; + port.write_request_to_send(false).unwrap(); +} + +#[allow(dead_code)] +pub async fn ping_bootloader_and_wait_for_response( + port: &mut SerialStream, +) -> Result { + let ping_pkt = [ESCAPE_CHAR, Command::Ping as u8]; + + let mut ret = BytesMut::with_capacity(200); + + for i in 0..30 { + println!("Iteration number {}", i); + let mut bytes_written = 0; + while bytes_written != ping_pkt.len() { + bytes_written += port.write_buf(&mut &ping_pkt[bytes_written..]).await?; + println!("Wrote {} bytes", bytes_written); + } + let mut read_bytes = 0; + while read_bytes < 2 { + read_bytes += port.read_buf(&mut ret).await?; + } + println!("Read {} bytes", read_bytes); + if ret[1] == Response::Pong as u8 { + return Ok(Response::from(ret[1])); + } + } + // TODO(Micu Ana): Add error handling + Ok(Response::from(ret[1])) +} + +#[allow(dead_code)] +pub async fn issue_command( + port: &mut SerialStream, + command: Command, + mut message: Vec, + sync: bool, + response_len: usize, + response_code: Response, +) -> Result<(Response, Vec), TockloaderError> { + // Setup a command to send to the bootloader and handle the response + // Generate the message to send to the bootloader + let mut i = 0; + while i < message.len() { + if message[i] == ESCAPE_CHAR { + // Escaped by replacing all 0xFC with two consecutive 0xFC - tock bootloader readme + message.insert(i + 1, ESCAPE_CHAR); + // Skip the inserted character + i += 2; + } else { + i += 1; + } + } + message.push(ESCAPE_CHAR); + message.push(command as u8); + + // If there should be a sync/reset message, prepend the outgoing message with it + if sync { + message.insert(0, SYNC_MESSAGE[0]); + message.insert(1, SYNC_MESSAGE[1]); + message.insert(2, SYNC_MESSAGE[2]); + } + + println!("Want to write {} bytes.", message.len()); + + // Write the command message + let mut bytes_written = 0; + while bytes_written != message.len() { + bytes_written += port.write_buf(&mut &message[bytes_written..]).await?; + } + println!("Wrote {} bytes", bytes_written); + + // Response has a two byte header, then response_len bytes + let bytes_to_read = 2 + response_len; + let mut ret = BytesMut::with_capacity(2); + + // We are waiting for 2 bytes to be read + let mut read_bytes = 0; + while read_bytes < 2 { + read_bytes += port.read_buf(&mut ret).await?; + } + println!("Read {} bytes", read_bytes); + println!("{:?}", ret); + + if ret[0] != ESCAPE_CHAR { + //TODO(Micu Ana): Add error handling + println!("Returning because first character is not escape"); + return Ok((Response::from(ret[1]), vec![])); + } + + if ret[1] != response_code.clone() as u8 { + //TODO(Micu Ana): Add error handling + dbg!(ret[1]); + dbg!(response_code.clone() as u8); + println!("Returning because second character is not response"); + return Ok((Response::from(ret[1]), vec![])); + } + + let mut new_data: Vec = Vec::new(); + let mut value = 2; + + dbg!(response_len); + if response_len != 0 { + while bytes_to_read > value { + value += port.read_buf(&mut new_data).await?; + } + dbg!(value); + + // De-escape and add array of read in the bytes + for i in 0..(new_data.len() - 1) { + if new_data[i] == ESCAPE_CHAR && new_data[i + 1] == ESCAPE_CHAR { + new_data.remove(i + 1); + } + } + + ret.extend_from_slice(&new_data); + } + + if ret.len() != (2 + response_len) { + // TODO(Micu Ana): Add error handling + return Ok((Response::from(ret[1]), vec![])); + } + + Ok((Response::from(ret[1]), ret[2..].to_vec())) +} diff --git a/tockloader-lib/src/lib.rs b/tockloader-lib/src/lib.rs index 52dc5da..20e57c2 100644 --- a/tockloader-lib/src/lib.rs +++ b/tockloader-lib/src/lib.rs @@ -3,14 +3,18 @@ // Copyright OXIDOS AUTOMOTIVE 2024. pub mod attributes; +pub(crate) mod bootloader_serial; pub mod connection; mod errors; pub mod tabs; +use std::time::Duration; + use attributes::app_attributes::AppAttributes; use attributes::general_attributes::GeneralAttributes; use attributes::system_attributes::SystemAttributes; +use bootloader_serial::{issue_command, ping_bootloader_and_wait_for_response, Command, Response}; use connection::Connection; use probe_rs::flashing::DownloadOptions; use probe_rs::probe::DebugProbeInfo; @@ -30,66 +34,83 @@ pub fn list_serial_ports() -> Vec { tokio_serial::available_ports().unwrap() } -pub async fn list_probe( +pub async fn list( choice: Connection, - core_index: &usize, + core_index: Option<&usize>, ) -> Result, TockloaderError> { match choice { Connection::ProbeRS(mut session) => { - let mut core = session.core(*core_index).unwrap(); - let system_attributes = SystemAttributes::read_system_attributes(&mut core); - Ok(AppAttributes::read_apps_data( + let mut core = session.core(*core_index.unwrap()).unwrap(); + let system_attributes = SystemAttributes::read_system_attributes_probe(&mut core); + Ok(AppAttributes::read_apps_data_probe( &mut core, system_attributes.appaddr.unwrap(), )) } - _ => { - // TODO(Micu Ana): Add error handling - Err(TockloaderError::NoPortAvailable) + 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 response = ping_bootloader_and_wait_for_response(&mut port).await?; + dbg!(response.clone()); + } + + let system_attributes = + SystemAttributes::read_system_attributes_serial(&mut port).await; + + Ok( + AppAttributes::read_apps_data_serial(&mut port, system_attributes.appaddr.unwrap()) + .await, + ) } } } -pub async fn list_serial(_choice: Connection) -> Result, TockloaderError> { - todo!(); -} - -pub async fn info_probe( +pub async fn info( choice: Connection, - core_index: &usize, + core_index: Option<&usize>, ) -> Result { match choice { Connection::ProbeRS(mut session) => { - let mut core = session.core(*core_index).unwrap(); - let system_attributes = SystemAttributes::read_system_attributes(&mut core); + let mut core = session.core(*core_index.unwrap()).unwrap(); + let system_attributes = SystemAttributes::read_system_attributes_probe(&mut core); let apps_details = - AppAttributes::read_apps_data(&mut core, system_attributes.appaddr.unwrap()); + AppAttributes::read_apps_data_probe(&mut core, system_attributes.appaddr.unwrap()); Ok(GeneralAttributes::new(system_attributes, apps_details)) } - _ => { - // TODO(Micu Ana): Add error handling - Err(TockloaderError::NoPortAvailable) + 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 response = ping_bootloader_and_wait_for_response(&mut port).await?; + dbg!(response.clone()); + } + + let system_attributes = + SystemAttributes::read_system_attributes_serial(&mut port).await; + let apps_details = + AppAttributes::read_apps_data_serial(&mut port, system_attributes.appaddr.unwrap()) + .await; + Ok(GeneralAttributes::new(system_attributes, apps_details)) } } } -pub async fn info_serial(_choice: Connection) -> Result { - todo!(); -} - -pub async fn install_app_probe_rs( +pub async fn install_app( choice: Connection, - core_index: &usize, + core_index: Option<&usize>, tab_file: Tab, ) -> Result<(), TockloaderError> { match choice { Connection::ProbeRS(mut session) => { // Get core - if not specified, by default is 0 // TODO (Micu Ana): Add error handling - let mut core = session.core(*core_index).unwrap(); + let mut core = session.core(*core_index.unwrap()).unwrap(); // Get board data - let system_attributes = SystemAttributes::read_system_attributes(&mut core); + let system_attributes = SystemAttributes::read_system_attributes_probe(&mut core); let board = system_attributes.board.unwrap(); let kernel_version = system_attributes.kernel_version.unwrap(); println!("Kernel version of board: {}", kernel_version); @@ -232,20 +253,164 @@ pub async fn install_app_probe_rs( // Finally, the data can be programmed loader.commit(&mut session, options).unwrap(); } + Ok(()) } + 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 response = ping_bootloader_and_wait_for_response(&mut port).await?; + dbg!(response.clone()); + } + + let system_attributes = + SystemAttributes::read_system_attributes_serial(&mut port).await; + let mut address = system_attributes.appaddr.unwrap(); + let board = system_attributes.board.unwrap(); + let kernel_version = system_attributes.kernel_version.unwrap(); + let arch = system_attributes.arch.unwrap(); + + // Verify if the specified app is compatible with board + if tab_file.is_compatible_with_board(&board) { + println!("Specified tab is compatible with board."); + } else { + panic!("Specified tab is not compatible with board."); + } + + // Verify if the specified app is compatible with kernel version + 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."); + } + + 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 + .unwrap(); + + let (_ver, _header_len, whole_len) = + match parse_tbf_header_lengths(&message[0..8].try_into().unwrap()) { + 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 mut binary = tab_file.extract_binary(&arch.clone()).unwrap(); + + 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); + dbg!(remaining); + 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.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..(binary_len / page_size) { + valid_pages.push(i.try_into().unwrap()); + } + } - _ => { - // TODO(Micu Ana): Add error handling - return Err(TockloaderError::NoPortAvailable); + // 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 { + 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 = (new_address as u32 + (i as usize * page_size) as u32) + .to_le_bytes() + .to_vec(); + dbg!(new_address as u32 + (i as usize * page_size) as u32); + dbg!(pkt.clone()); + // 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 + .unwrap(); + } + + new_address += binary.len() as u64; + + let pkt = (new_address as u32).to_le_bytes().to_vec(); + dbg!(pkt.clone()); + + let _ = issue_command(&mut port, Command::ErasePage, pkt, true, 0, Response::OK) + .await + .unwrap(); + Ok(()) } } - Ok(()) -} - -pub async fn install_app_serial( - _choice: Connection, - _board: &str, - _tab_file: Tab, -) -> Result<(), TockloaderError> { - todo!(); }