diff --git a/Cargo.toml b/Cargo.toml index f7041e9..999ed3f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [workspace] resolver = "2" -members = [ +members = [ "bootloader", "examples/simple", "flashloader", "va416xx", @@ -26,7 +26,6 @@ incremental = false lto = 'fat' opt-level = 3 # <- overflow-checks = false # <- -strip = true [profile.small] inherits = "release" @@ -35,5 +34,3 @@ debug-assertions = false # <- lto = true opt-level = 'z' # <- overflow-checks = false # <- -strip = true - diff --git a/bootloader/src/main.rs b/bootloader/src/main.rs index b29efc4..7866166 100644 --- a/bootloader/src/main.rs +++ b/bootloader/src/main.rs @@ -114,6 +114,7 @@ fn main() -> ! { if DEBUG_PRINTOUTS { rprintln!("both images corrupt! booting image A"); } + loop {} // TODO: Shift a CCSDS packet out to inform host/OBC about image corruption. // Both images seem to be corrupt. Boot default image A. boot_app(AppSel::A, &cp) diff --git a/flashloader/Cargo.toml b/flashloader/Cargo.toml index 8bce51c..a01e8a4 100644 --- a/flashloader/Cargo.toml +++ b/flashloader/Cargo.toml @@ -7,9 +7,27 @@ edition = "2021" cortex-m = "0.7" cortex-m-rt = "0.7" embedded-hal = "1" +embedded-hal-nb = "1" panic-rtt-target = { version = "0.1.3" } rtt-target = { version = "0.5" } +rtt-log = "0.3" +log = "0.4" crc = "3" +rtic-sync = "1" + +[dependencies.once_cell] +version = "1" +default-features = false +features = ["critical-section"] + +[dependencies.spacepackets] +version = "0.11" +default-features = false + +[dependencies.cobs] +git = "https://github.com/robamu/cobs.rs.git" +branch = "all_features" +default-features = false [dependencies.va416xx-hal] path = "../va416xx-hal" diff --git a/flashloader/src/lib.rs b/flashloader/src/lib.rs new file mode 100644 index 0000000..5c9605a --- /dev/null +++ b/flashloader/src/lib.rs @@ -0,0 +1,9 @@ +#![no_std] + +#[cfg(test)] +mod tests { + #[test] + fn simple() { + assert_eq!(1 + 1, 2); + } +} diff --git a/flashloader/src/main.rs b/flashloader/src/main.rs index f1ac91f..0c85248 100644 --- a/flashloader/src/main.rs +++ b/flashloader/src/main.rs @@ -18,48 +18,18 @@ #![no_main] #![no_std] -use crc::{Crc, CRC_16_IBM_3740}; +use embedded_hal_nb::serial::Read; +use once_cell::sync::OnceCell; use panic_rtt_target as _; -use va416xx_hal::{ - edac, - pac::{self}, - time::Hertz, - wdt::Wdt, -}; +use va416xx_hal::{clock::Clocks, edac, pac, time::Hertz, wdt::Wdt}; const EXTCLK_FREQ: u32 = 40_000_000; -const WITH_WDT: bool = true; -const WDT_FREQ_MS: u32 = 50; -const DEBUG_PRINTOUTS: bool = true; +const COBS_FRAME_SEPARATOR: u8 = 0x0; +const MAX_PACKET_SIZE: usize = 1024; +const MAX_FRAME_SIZE: usize = cobs::max_encoding_length(MAX_PACKET_SIZE); +const UART_BAUDRATE: u32 = 115200; -// Important bootloader addresses and offsets, vector table information. - -const BOOTLOADER_START_ADDR: u32 = 0x0; -const BOOTLOADER_END_ADDR: u32 = 0x4000; -const BOOTLOADER_CRC_ADDR: u32 = 0x3FFE; -const APP_A_START_ADDR: u32 = 0x4000; -pub const APP_A_END_ADDR: u32 = 0x22000; -// The actual size of the image which is relevant for CRC calculation. -const APP_A_SIZE_ADDR: u32 = 0x21FF8; -const APP_A_CRC_ADDR: u32 = 0x21FFC; -const APP_B_START_ADDR: u32 = 0x22000; -pub const APP_B_END_ADDR: u32 = 0x40000; -// The actual size of the image which is relevant for CRC calculation. -const APP_B_SIZE_ADDR: u32 = 0x3FFF8; -const APP_B_CRC_ADDR: u32 = 0x3FFFC; -pub const APP_IMG_SZ: u32 = 0x1E000; - -pub const VECTOR_TABLE_OFFSET: u32 = 0x0; -pub const VECTOR_TABLE_LEN: u32 = 0x350; -pub const RESET_VECTOR_OFFSET: u32 = 0x4; - -const CRC_ALGO: Crc = Crc::::new(&CRC_16_IBM_3740); - -#[derive(Debug, Copy, Clone, PartialEq, Eq)] -enum AppSel { - A, - B, -} +const BOOT_NVM_MEMORY_ID: u8 = 1; pub trait WdtInterface { fn feed(&self); @@ -75,25 +45,64 @@ impl WdtInterface for OptWdt { } } -#[rtic::app(device = pac)] +static CLOCKS: OnceCell = OnceCell::new(); + +#[rtic::app(device = pac, dispatchers = [U1, U2, U3])] mod app { use super::*; + use embedded_hal_nb::nb; use panic_rtt_target as _; + use rtic_monotonics::systick::ExtU32; use rtic_monotonics::systick::Systick; - use rtt_target::{rprintln, rtt_init_default}; - use va416xx_hal::{clock::ClkgenExt, edac, pac}; + use rtic_sync::{ + channel::{Receiver, Sender}, + make_channel, + }; + use rtt_target::rprintln; + use spacepackets::ecss::PusServiceId; + use spacepackets::ecss::{tc::PusTcReader, PusPacket}; + use va416xx_hal::{ + clock::ClkgenExt, + edac, + gpio::PinsG, + nvm::Nvm, + pac, + uart::{self, Uart}, + }; use crate::{setup_edac, EXTCLK_FREQ}; + #[derive(Default, Debug, Copy, Clone, PartialEq, Eq)] + pub enum CobsReaderStates { + #[default] + WaitingForStart, + WatingForEnd, + FrameOverflow, + } + #[local] - struct Local {} + struct Local { + uart_rx: uart::Rx, + uart_tx: uart::Tx, + cobs_reader_state: CobsReaderStates, + tc_tx: TcTx, + tc_rx: TcRx, + rom_spi: Option, + } #[shared] - struct Shared {} + struct Shared { + decode_buffer_busy: bool, + decode_buf: [u8; MAX_PACKET_SIZE], + } + + pub type TcTx = Sender<'static, usize, 2>; + pub type TcRx = Receiver<'static, usize, 2>; #[init] fn init(mut cx: init::Context) -> (Shared, Local) { - rtt_init_default!(); + //rtt_init_default!(); + rtt_log::init(); rprintln!("-- Vorago flashloader --"); // Initialize the systick interrupt & obtain the token to prove that we did let systick_mono_token = rtic_monotonics::create_systick_token!(); @@ -107,8 +116,36 @@ mod app { .unwrap(); setup_edac(&mut cx.device.sysconfig); + let gpiob = PinsG::new(&mut cx.device.sysconfig, cx.device.portg); + let tx = gpiob.pg0.into_funsel_1(); + let rx = gpiob.pg1.into_funsel_1(); + + let uart0 = Uart::new( + cx.device.uart0, + (tx, rx), + Hertz::from_raw(UART_BAUDRATE), + &mut cx.device.sysconfig, + &clocks, + ); + let (tx, rx) = uart0.split(); + let (tc_tx, tc_rx) = make_channel!(usize, 2); + Systick::start(cx.core.SYST, clocks.sysclk().raw(), systick_mono_token); - (Shared {}, Local {}) + CLOCKS.set(clocks).unwrap(); + ( + Shared { + decode_buffer_busy: false, + decode_buf: [0; MAX_PACKET_SIZE], + }, + Local { + uart_rx: rx, + uart_tx: tx, + cobs_reader_state: CobsReaderStates::default(), + tc_tx, + tc_rx, + rom_spi: Some(cx.device.spi3), + }, + ) } // `shared` cannot be accessed from this context @@ -118,6 +155,169 @@ mod app { loop {} } + #[task( + priority = 3, + local=[ + read_buf: [u8;MAX_FRAME_SIZE] = [0; MAX_FRAME_SIZE], + uart_rx, + cobs_reader_state, + tc_tx + ], + shared=[decode_buffer_busy, decode_buf] + )] + async fn uart_reader_task(mut cx: uart_reader_task::Context) { + let mut current_idx = 0; + loop { + match nb::block!(cx.local.uart_rx.read()) { + Ok(byte) => match cx.local.cobs_reader_state { + CobsReaderStates::WaitingForStart => { + if byte == COBS_FRAME_SEPARATOR { + *cx.local.cobs_reader_state = CobsReaderStates::WatingForEnd; + } + } + CobsReaderStates::WatingForEnd => { + if byte == COBS_FRAME_SEPARATOR { + let mut sending_failed = false; + let mut decoding_error = false; + let mut decode_buffer_busy = false; + cx.shared.decode_buffer_busy.lock(|busy| { + if *busy { + decode_buffer_busy = true; + } else { + cx.shared.decode_buf.lock(|buf| { + match cobs::decode(&cx.local.read_buf[..current_idx], buf) { + Ok(packet_len) => { + if cx.local.tc_tx.try_send(packet_len).is_err() { + sending_failed = true; + } + } + Err(_) => { + decoding_error = true; + } + } + }); + *busy = true; + } + }); + if sending_failed { + log::warn!("sending TC packet failed, queue full"); + } + if decoding_error { + log::warn!("decoding error"); + } + if decode_buffer_busy { + log::warn!("decode buffer busy. data arriving too fast"); + } + } else if current_idx >= cx.local.read_buf.len() { + *cx.local.cobs_reader_state = CobsReaderStates::FrameOverflow; + } else { + cx.local.read_buf[current_idx] = byte; + current_idx += 1; + } + } + CobsReaderStates::FrameOverflow => { + if byte == COBS_FRAME_SEPARATOR { + *cx.local.cobs_reader_state = CobsReaderStates::WaitingForStart; + current_idx = 0; + } + } + }, + Err(e) => match e { + uart::Error::Overrun => todo!(), + uart::Error::FramingError => todo!(), + uart::Error::ParityError => todo!(), + uart::Error::BreakCondition => todo!(), + uart::Error::TransferPending => todo!(), + uart::Error::BufferTooShort => todo!(), + }, + } + } + } + + #[task( + priority = 2, + local=[ + read_buf: [u8;MAX_FRAME_SIZE] = [0; MAX_FRAME_SIZE], + tc_rx, + rom_spi + ], + shared=[decode_buffer_busy, decode_buf] + )] + async fn pus_tc_handler(mut cx: pus_tc_handler::Context) { + loop { + let _ = cx.local.tc_rx.recv().await; + // We still copy the data to a local buffer, so the exchange buffer can already be used + // for the next packet / decode process. + cx.shared + .decode_buf + .lock(|buf| cx.local.read_buf[0..buf.len()].copy_from_slice(buf)); + cx.shared.decode_buffer_busy.lock(|busy| *busy = false); + match PusTcReader::new(cx.local.read_buf) { + Ok((pus_tc, _)) => { + if pus_tc.service() == PusServiceId::Test as u8 && pus_tc.subservice() == 1 { + log::info!("Received ping TC"); + } else if pus_tc.service() == PusServiceId::MemoryManagement as u8 { + // Raw memory write TC + if pus_tc.subservice() == 2 { + let app_data = pus_tc.app_data(); + if app_data.len() < 10 { + log::warn!( + "app data for raw memory write is too short: {}", + app_data.len() + ); + } + let memory_id = app_data[0]; + if memory_id != BOOT_NVM_MEMORY_ID { + log::warn!("memory ID {} not supported", memory_id); + // TODO: Error reporting + return; + } + let offset = u32::from_be_bytes(app_data[2..6].try_into().unwrap()); + let data_len = u32::from_be_bytes(app_data[6..10].try_into().unwrap()); + if 10 + data_len as usize > app_data.len() { + log::warn!( + "invalid data length {} for raw mem write detected", + data_len + ); + // TODO: Error reporting + return; + } + let data = &app_data[10..10 + data_len as usize]; + log::info!("writing {} bytes at offset {} to NVM", data_len, offset); + // Safety: We only use this for NVM handling and we only do NVM + // handling here. + let mut sys_cfg = unsafe { pac::Sysconfig::steal() }; + let nvm = Nvm::new( + &mut sys_cfg, + cx.local.rom_spi.take().unwrap(), + CLOCKS.get().as_ref().unwrap(), + ); + nvm.write_data(offset, data); + *cx.local.rom_spi = Some(nvm.release(&mut sys_cfg)); + log::info!("NVM operation done"); + } + } + } + Err(e) => { + log::warn!("PUS TC error: {}", e); + }, + } + } + } + + #[task( + priority = 1, + local=[ + uart_tx, + ], + shared=[] + )] + async fn pus_tm_tx_handler(_cx: pus_tm_tx_handler::Context) { + loop { + Systick::delay(500.millis()).await; + } + } + #[task(binds = EDAC_SBE, priority = 1)] fn edac_sbe_isr(_cx: edac_sbe_isr::Context) { // TODO: Send some command via UART for notification purposes. Also identify the problematic diff --git a/va416xx-hal/src/nvm.rs b/va416xx-hal/src/nvm.rs index 72e245e..5892df0 100644 --- a/va416xx-hal/src/nvm.rs +++ b/va416xx-hal/src/nvm.rs @@ -44,7 +44,7 @@ pub const BP_0_ENABLE_MASK: u8 = 1 << 2; pub const BP_1_ENABLE_MASK: u8 = 1 << 3; pub struct Nvm { - spi: pac::Spi3, + spi: Option, } #[derive(Debug, PartialEq, Eq)] @@ -90,7 +90,7 @@ impl Nvm { // programmers guide spi.ctrl1().modify(|_, w| w.enable().set_bit()); - let mut nvm = Self { spi }; + let mut nvm = Self { spi: Some(spi) }; nvm.disable_write_prot(); nvm } @@ -110,36 +110,40 @@ impl Nvm { self.write_single(FRAM_WRSR); self.write_with_bmstop(0x00); } + #[inline(always)] + pub fn spi(&self) -> &pac::Spi3 { + self.spi.as_ref().unwrap() + } #[inline(always)] pub fn write_single(&self, word: u8) { - self.spi.data().write(|w| unsafe { w.bits(word as u32) }) + self.spi().data().write(|w| unsafe { w.bits(word as u32) }) } #[inline(always)] pub fn write_with_bmstop(&self, word: u8) { - self.spi + self.spi() .data() .write(|w| unsafe { w.bits(BMSTART_BMSTOP_MASK | word as u32) }) } #[inline(always)] pub fn wait_for_tx_idle(&self) { - while self.spi.status().read().tfe().bit_is_clear() { + while self.spi().status().read().tfe().bit_is_clear() { cortex_m::asm::nop(); } } #[inline(always)] pub fn wait_for_rx_available(&self) { - while !self.spi.status().read().rne().bit_is_set() { + while !self.spi().status().read().rne().bit_is_set() { cortex_m::asm::nop(); } } #[inline(always)] pub fn read_single_word(&self) -> u32 { - self.spi.data().read().bits() + self.spi().data().read().bits() } pub fn write_data(&self, addr: u32, data: &[u8]) { @@ -151,13 +155,13 @@ impl Nvm { self.write_single(mid_addr_byte(addr)); self.write_single(lsb_addr_byte(addr)); for byte in data.iter().take(data.len() - 1) { - while self.spi.status().read().tnf().bit_is_clear() { + while self.spi().status().read().tnf().bit_is_clear() { cortex_m::asm::nop(); } self.write_single(*byte); self.read_single_word(); } - while self.spi.status().read().tnf().bit_is_clear() { + while self.spi().status().read().tnf().bit_is_clear() { cortex_m::asm::nop(); } self.write_with_bmstop(*data.last().unwrap()); @@ -194,13 +198,19 @@ impl Nvm { Ok(()) } - /// This function releases the ROM SPI and enables chip write protection again. - pub fn release(self) -> pac::Spi3 { + /// Enable write-protection and disables the peripheral clock. + pub fn shutdown(&mut self, sys_cfg: &mut pac::Sysconfig) { self.wait_for_tx_idle(); self.write_with_bmstop(FRAM_WREN); self.wait_for_tx_idle(); self.write_single(WPEN_ENABLE_MASK | BP_0_ENABLE_MASK | BP_1_ENABLE_MASK); - self.spi + crate::clock::disable_peripheral_clock(sys_cfg, pac::Spi3::PERIPH_SEL); + } + + /// This function calls [Self::shutdown] and gives back the peripheral structure. + pub fn release(mut self, sys_cfg: &mut pac::Sysconfig) -> pac::Spi3 { + self.shutdown(sys_cfg); + self.spi.take().unwrap() } fn common_read_start(&self, addr: u32) { @@ -218,3 +228,10 @@ impl Nvm { } } } + +/// Call [Self::shutdown] on drop. +impl Drop for Nvm { + fn drop(&mut self) { + self.shutdown(unsafe { &mut pac::Sysconfig::steal() }); + } +}