Finish flashing and crash dump
This commit is contained in:
parent
bca8965924
commit
c3ea45f839
|
@ -21,3 +21,4 @@ wgpu = "0.12"
|
|||
winit = "0.26.1"
|
||||
egui-winit = "0.17.0"
|
||||
modular-bitfield = "0.11.2"
|
||||
static_assertions = "1.1.0"
|
|
@ -0,0 +1,175 @@
|
|||
use std::{
|
||||
fs::File,
|
||||
io::{Read, Write},
|
||||
sync::{
|
||||
atomic::{AtomicBool, AtomicU32, Ordering},
|
||||
Arc, Mutex, RwLock,
|
||||
}, num::Wrapping, ops::DerefMut,
|
||||
};
|
||||
|
||||
use ecu_diagnostics::{kwp2000::{Kwp2000DiagnosticServer, SessionType, ResetMode}, DiagServerResult, DiagnosticServer, DiagError};
|
||||
use egui::*;
|
||||
use epi::*;
|
||||
use nfd::Response;
|
||||
|
||||
use crate::{
|
||||
usb_hw::{diag_usb::Nag52USB, flasher::{self, bin::{Firmware, load_binary}}},
|
||||
window::{InterfacePage, PageAction},
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub enum ReadState {
|
||||
None,
|
||||
Prepare,
|
||||
ReadingBlock { id: u32, out_of: u32, bytes_written: u32 },
|
||||
Completed,
|
||||
Aborted(String)
|
||||
}
|
||||
|
||||
impl ReadState {
|
||||
pub fn is_done(&self) -> bool {
|
||||
match self {
|
||||
ReadState::None => true,
|
||||
ReadState::Prepare => false,
|
||||
ReadState::ReadingBlock { id, out_of, bytes_written } => false,
|
||||
ReadState::Completed => true,
|
||||
ReadState::Aborted(_) => true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct CrashAnalyzerUI {
|
||||
server: Arc<Mutex<Kwp2000DiagnosticServer>>,
|
||||
read_state: Arc<RwLock<ReadState>>,
|
||||
}
|
||||
|
||||
impl CrashAnalyzerUI {
|
||||
pub fn new(server:Arc<Mutex<Kwp2000DiagnosticServer>>) -> Self {
|
||||
Self {
|
||||
server,
|
||||
read_state: Arc::new(RwLock::new(ReadState::None)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Return structure
|
||||
/// 1. Coredump offset
|
||||
/// 2. Coredump size
|
||||
/// 3. Block size
|
||||
fn init_flash_mode(server: &mut Kwp2000DiagnosticServer) -> DiagServerResult<(u32, u32, u32)> {
|
||||
server.set_diagnostic_session_mode(SessionType::Reprogramming)?;
|
||||
|
||||
// First request coredump info
|
||||
let mut res = server.read_custom_local_identifier(0x24)?;
|
||||
if res.len() != 8 {
|
||||
return Err(DiagError::InvalidResponseLength)
|
||||
}
|
||||
let address = u32::from_le_bytes(res[0..4].try_into().unwrap());
|
||||
let size = u32::from_le_bytes(res[4..8].try_into().unwrap());
|
||||
if size == 0 {
|
||||
return Ok((0,0,0));
|
||||
}
|
||||
let mut upload_req = vec![0x35, 0x31, 0x00, 0x00, 0x00];
|
||||
upload_req.push((size >> 16) as u8);
|
||||
upload_req.push((size >> 8) as u8);
|
||||
upload_req.push((size >> 0) as u8);
|
||||
res = server.send_byte_array_with_response(&upload_req)?;
|
||||
if res.len() != 3 {
|
||||
return Err(DiagError::InvalidResponseLength)
|
||||
}
|
||||
let bs: u32 = ((res[1] as u32) << 8) | res[2] as u32;
|
||||
Ok((address, size, bs))
|
||||
}
|
||||
|
||||
fn on_flash_end(server: &mut Kwp2000DiagnosticServer, read: Vec<u8>) -> DiagServerResult<()> {
|
||||
server.send_byte_array_with_response(&[0x37])?;
|
||||
File::create("dump.elf").unwrap().write_all(&read[20..]); // First 20 bytes are header of partition. We don't need it
|
||||
Ok(())
|
||||
}
|
||||
|
||||
impl InterfacePage for CrashAnalyzerUI {
|
||||
fn make_ui(
|
||||
&mut self,
|
||||
ui: &mut egui::Ui,
|
||||
frame: &epi::Frame,
|
||||
) -> crate::window::PageAction {
|
||||
ui.heading("Crash Analyzer");
|
||||
ui.label(
|
||||
RichText::new("Caution! Only use when car is off").color(Color32::from_rgb(255, 0, 0)),
|
||||
);
|
||||
let state = self.read_state.read().unwrap();
|
||||
if state.is_done() {
|
||||
if ui.button("Read coredump ELF").clicked() {
|
||||
let c = self.server.clone();
|
||||
let state_c = self.read_state.clone();
|
||||
std::thread::spawn(move|| {
|
||||
let mut lock = c.lock().unwrap();
|
||||
*state_c.write().unwrap() = ReadState::Prepare;
|
||||
match init_flash_mode(&mut lock.deref_mut()) {
|
||||
Err(e) => {
|
||||
*state_c.write().unwrap() = ReadState::Aborted(format!("ECU rejected flash programming mode: {}", e))
|
||||
},
|
||||
Ok(size) => {
|
||||
println!("OK {:?}", size);
|
||||
if size.1 == 0x00 {
|
||||
println!("No coredump on flash");
|
||||
*state_c.write().unwrap() = ReadState::Completed;
|
||||
} else {
|
||||
println!("ESP Coredump found. Will read from address 0x{:08X} {} bytes in {} byte segments", size.0, size.1, size.2);
|
||||
let block_count = size.1 / size.2;
|
||||
let mut data: Vec<u8> = Vec::with_capacity(size.1 as usize);
|
||||
let mut i = 0;
|
||||
while (data.len() as u32) < size.1 {
|
||||
match lock.send_byte_array_with_response(&[0x36, ((i+1) & 0xFF) as u8]) {
|
||||
Ok(p) => {
|
||||
data.extend_from_slice(&p[2..]);
|
||||
i+=1;
|
||||
*state_c.write().unwrap() = ReadState::ReadingBlock { id: i+1, out_of: block_count, bytes_written: data.len() as u32 };
|
||||
},
|
||||
Err(e) => {
|
||||
*state_c.write().unwrap() = ReadState::Aborted(format!("ECU rejected transfer data: {}", e));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
on_flash_end(&mut lock.deref_mut(), data);
|
||||
}
|
||||
*state_c.write().unwrap() = ReadState::Completed;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
} else {
|
||||
ui.label("Coredump reading in progress...");
|
||||
ui.label("DO NOT EXIT THE APP");
|
||||
ui.ctx().request_repaint();
|
||||
}
|
||||
|
||||
match self.read_state.read().unwrap().clone() {
|
||||
ReadState::None => {},
|
||||
ReadState::Prepare => {
|
||||
egui::widgets::ProgressBar::new(0.0).show_percentage().desired_width(300.0).ui(ui);
|
||||
ui.label("Preparing ECU...");
|
||||
},
|
||||
ReadState::ReadingBlock { id, out_of, bytes_written } => {
|
||||
egui::widgets::ProgressBar::new((id as f32) / (out_of as f32)).show_percentage().desired_width(300.0).ui(ui);
|
||||
ui.label(format!("Bytes read: {}", bytes_written));
|
||||
},
|
||||
ReadState::Completed => {
|
||||
ui.label(RichText::new("Coredump ELF saved as dump.elf!").color(Color32::from_rgb(0, 255, 0)));
|
||||
},
|
||||
ReadState::Aborted(r) => {
|
||||
ui.label(RichText::new(format!("Coredump read ABORTED! Reason: {}", r)).color(Color32::from_rgb(255, 0, 0)));
|
||||
},
|
||||
}
|
||||
return PageAction::SetBackButtonState(true);
|
||||
}
|
||||
|
||||
fn get_title(&self) -> &'static str {
|
||||
"Ultimate-Nag52 coredump analyzer"
|
||||
}
|
||||
|
||||
fn get_status_bar(&self) -> Option<Box<dyn crate::window::StatusBar>> {
|
||||
None
|
||||
}
|
||||
}
|
|
@ -3,7 +3,7 @@ use std::{
|
|||
io::Read,
|
||||
sync::{
|
||||
atomic::{AtomicBool, AtomicU32, Ordering},
|
||||
Arc, Mutex,
|
||||
Arc, Mutex, RwLock,
|
||||
}, num::Wrapping, ops::DerefMut,
|
||||
};
|
||||
|
||||
|
@ -37,14 +37,34 @@ impl Default for FlashDataFormatted {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub enum FlashState {
|
||||
None,
|
||||
Prepare,
|
||||
WritingBlock { id: u32, out_of: u32, bytes_written: u32 },
|
||||
Verify,
|
||||
Completed,
|
||||
Aborted(String)
|
||||
}
|
||||
|
||||
impl FlashState {
|
||||
pub fn is_done(&self) -> bool {
|
||||
match self {
|
||||
FlashState::None => true,
|
||||
FlashState::Prepare => false,
|
||||
FlashState::WritingBlock { id, out_of, bytes_written } => false,
|
||||
FlashState::Verify => false,
|
||||
FlashState::Completed => true,
|
||||
FlashState::Aborted(_) => true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct FwUpdateUI {
|
||||
server: Arc<Mutex<Kwp2000DiagnosticServer>>,
|
||||
elf_path: Option<String>,
|
||||
flashing: Arc<AtomicBool>,
|
||||
firmware: Option<Firmware>,
|
||||
progress: Arc<AtomicU32>,
|
||||
//flash_err: Arc<Mutex<Option<std::result::Result<(), espflash_un52::Error>>>>,
|
||||
reconnect: Arc<AtomicBool>,
|
||||
flash_state: Arc<RwLock<FlashState>>,
|
||||
}
|
||||
|
||||
pub struct FlasherMutate {}
|
||||
|
@ -54,35 +74,32 @@ impl FwUpdateUI {
|
|||
Self {
|
||||
server,
|
||||
elf_path: None,
|
||||
flashing: Arc::new(AtomicBool::new(false)),
|
||||
firmware: None,
|
||||
progress: Arc::new(AtomicU32::new(0)),
|
||||
//flash_err: Arc::new(Mutex::new(None)),
|
||||
reconnect: Arc::new(AtomicBool::new(false)),
|
||||
flash_state: Arc::new(RwLock::new(FlashState::None)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn init_flash_mode(&self, server: &mut Kwp2000DiagnosticServer, flash_size: u32) -> DiagServerResult<u32> {
|
||||
server.set_diagnostic_session_mode(SessionType::Reprogramming)?;
|
||||
let mut req: Vec<u8> = vec![0x34, 0x00, 0x00, 0x00, 0x00];
|
||||
req.push((flash_size >> 16) as u8);
|
||||
req.push((flash_size >> 8 ) as u8);
|
||||
req.push((flash_size) as u8);
|
||||
let resp = server.send_byte_array_with_response(&req)?;
|
||||
let bs = (resp[1] as u16) << 8 | resp[2] as u16;
|
||||
Ok(bs as u32)
|
||||
}
|
||||
fn init_flash_mode(server: &mut Kwp2000DiagnosticServer, flash_size: u32) -> DiagServerResult<u32> {
|
||||
server.set_diagnostic_session_mode(SessionType::Reprogramming)?;
|
||||
let mut req: Vec<u8> = vec![0x34, 0x00, 0x00, 0x00, 0x00];
|
||||
req.push((flash_size >> 16) as u8);
|
||||
req.push((flash_size >> 8 ) as u8);
|
||||
req.push((flash_size) as u8);
|
||||
let resp = server.send_byte_array_with_response(&req)?;
|
||||
let bs = (resp[1] as u16) << 8 | resp[2] as u16;
|
||||
Ok(bs as u32)
|
||||
}
|
||||
|
||||
fn on_flash_end(&self, server: &mut Kwp2000DiagnosticServer) -> DiagServerResult<()> {
|
||||
server.send_byte_array_with_response(&[0x37])?;
|
||||
let status = server.send_byte_array_with_response(&[0x31, 0xE1])?;
|
||||
if status[1] == 0x00 {
|
||||
eprintln!("ECU Flash check OK! Rebooting");
|
||||
return server.reset_ecu(ResetMode::PowerOnReset)
|
||||
} else {
|
||||
eprintln!("ECU Flash check failed :(");
|
||||
return Err(DiagError::NotSupported)
|
||||
}
|
||||
fn on_flash_end(server: &mut Kwp2000DiagnosticServer) -> DiagServerResult<()> {
|
||||
server.send_byte_array_with_response(&[0x37])?;
|
||||
let status = server.send_byte_array_with_response(&[0x31, 0xE1])?;
|
||||
if status[2] == 0x00 {
|
||||
eprintln!("ECU Flash check OK! Rebooting");
|
||||
return server.reset_ecu(ResetMode::PowerOnReset)
|
||||
} else {
|
||||
eprintln!("ECU Flash check failed :(");
|
||||
return Err(DiagError::NotSupported)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -126,37 +143,77 @@ firmware.header.get_idf_version(),
|
|||
firmware.header.get_time(),
|
||||
firmware.header.get_date()
|
||||
)));
|
||||
if !self.flashing.load(Ordering::Relaxed) {
|
||||
let state = self.flash_state.read().unwrap();
|
||||
if state.is_done() {
|
||||
if ui.button("Flash firmware").clicked() {
|
||||
let mut lock = self.server.lock().unwrap();
|
||||
match self.init_flash_mode(&mut lock.deref_mut(), firmware.raw.len() as u32) {
|
||||
Err(e) => eprintln!("ECU flash mode reject! {}", e),
|
||||
Ok(size) => {
|
||||
// Start the flasher!
|
||||
let arr = firmware.raw.chunks(size as usize);
|
||||
let mut failure = false;
|
||||
for (block_id, block) in arr.enumerate() {
|
||||
let mut req = vec![0x36, ((block_id+1) & 0xFF) as u8]; // [Transfer data request, block counter]
|
||||
req.extend_from_slice(block); // Block to transfer
|
||||
if let Err(e) = lock.send_byte_array_with_response(&req) {
|
||||
eprintln!("Writing failed! Error {}", e);
|
||||
failure = true;
|
||||
break;
|
||||
let c = self.server.clone();
|
||||
let state_c = self.flash_state.clone();
|
||||
let fw = firmware.clone();
|
||||
std::thread::spawn(move|| {
|
||||
let mut lock = c.lock().unwrap();
|
||||
*state_c.write().unwrap() = FlashState::Prepare;
|
||||
match init_flash_mode(&mut lock.deref_mut(), fw.raw.len() as u32) {
|
||||
Err(e) => {
|
||||
*state_c.write().unwrap() = FlashState::Aborted(format!("ECU rejected flash programming mode: {}", e))
|
||||
},
|
||||
Ok(size) => {
|
||||
// Start the flasher!
|
||||
let arr = fw.raw.chunks(size as usize);
|
||||
let total_blocks = arr.len() as u32;
|
||||
let mut failure = false;
|
||||
let mut bytes_written = 0;
|
||||
for (block_id, block) in arr.enumerate() {
|
||||
let mut req = vec![0x36, ((block_id+1) & 0xFF) as u8]; // [Transfer data request, block counter]
|
||||
req.extend_from_slice(block); // Block to transfer
|
||||
if let Err(e) = lock.send_byte_array_with_response(&req) {
|
||||
*state_c.write().unwrap() = FlashState::Aborted(format!("ECU failed to write data to flash: {}", e));
|
||||
eprintln!("Writing failed! Error {}", e);
|
||||
failure = true;
|
||||
break;
|
||||
} else {
|
||||
bytes_written += block.len() as u32;
|
||||
*state_c.write().unwrap() = FlashState::WritingBlock { id: (block_id+1) as u32, out_of: total_blocks, bytes_written }
|
||||
}
|
||||
}
|
||||
}
|
||||
if !failure {
|
||||
if let Err(e) = self.on_flash_end(&mut lock) {
|
||||
eprintln!("ECU flash check error {}", e)
|
||||
if !failure {
|
||||
*state_c.write().unwrap() = FlashState::Verify;
|
||||
if let Err(e) = on_flash_end(&mut lock) {
|
||||
*state_c.write().unwrap() = FlashState::Aborted(format!("ECU failed to verify flash: {}", e))
|
||||
} else {
|
||||
*state_c.write().unwrap() = FlashState::Completed;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
} else {
|
||||
ui.label("Flashing in progress...");
|
||||
ui.label("DO NOT EXIT THE APP");
|
||||
return PageAction::SetBackButtonState(false);
|
||||
ui.ctx().request_repaint();
|
||||
}
|
||||
match self.flash_state.read().unwrap().clone() {
|
||||
FlashState::None => {},
|
||||
FlashState::Prepare => {
|
||||
egui::widgets::ProgressBar::new(0.0).show_percentage().desired_width(300.0).ui(ui);
|
||||
ui.label("Preparing ECU...");
|
||||
},
|
||||
FlashState::WritingBlock { id, out_of, bytes_written } => {
|
||||
egui::widgets::ProgressBar::new((id as f32)/(out_of as f32)).show_percentage().desired_width(300.0).ui(ui);
|
||||
ui.label(format!("Bytes written: {}", bytes_written));
|
||||
},
|
||||
FlashState::Verify => {
|
||||
egui::widgets::ProgressBar::new(100.0).show_percentage().desired_width(300.0).ui(ui);
|
||||
ui.label("Verifying written data...");
|
||||
},
|
||||
FlashState::Completed => {
|
||||
ui.label(RichText::new("Flashing completed successfully!").color(Color32::from_rgb(0, 255, 0)));
|
||||
},
|
||||
FlashState::Aborted(r) => {
|
||||
ui.label(RichText::new(format!("Flashing was ABORTED! Reason: {}", r)).color(Color32::from_rgb(255, 0, 0)));
|
||||
},
|
||||
}
|
||||
return PageAction::SetBackButtonState(false);
|
||||
}
|
||||
return PageAction::SetBackButtonState(true);
|
||||
}
|
||||
|
|
|
@ -13,7 +13,7 @@ use crate::{
|
|||
window::{InterfacePage, PageAction, StatusBar},
|
||||
};
|
||||
|
||||
use super::{firmware_update::FwUpdateUI, status_bar::MainStatusBar, configuration::ConfigPage};
|
||||
use super::{firmware_update::FwUpdateUI, status_bar::MainStatusBar, configuration::ConfigPage, crashanalyzer::CrashAnalyzerUI};
|
||||
|
||||
use ecu_diagnostics::kwp2000::*;
|
||||
use crate::ui::diagnostics::DiagnosticsPage;
|
||||
|
@ -88,6 +88,9 @@ impl InterfacePage for MainPage {
|
|||
if v.button("Firmware updater").on_disabled_hover_ui(|u| {u.label("Broken, will be added soon!");}).clicked() {
|
||||
create_page = Some(PageAction::Add(Box::new(FwUpdateUI::new(self.diag_server.clone()))));
|
||||
}
|
||||
if v.button("Crash analyzer").clicked() {
|
||||
create_page = Some(PageAction::Add(Box::new(CrashAnalyzerUI::new(self.diag_server.clone()))));
|
||||
}
|
||||
if v.button("Diagnostics").clicked() {
|
||||
create_page = Some(PageAction::Add(Box::new(DiagnosticsPage::new(self.diag_server.clone(), self.bar.clone()))));
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ pub mod main;
|
|||
pub mod status_bar;
|
||||
pub mod diagnostics;
|
||||
pub mod configuration;
|
||||
pub mod crashanalyzer;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub enum StatusText {
|
||||
|
|
Loading…
Reference in New Issue