stupid nvm

This commit is contained in:
Robin Müller 2024-07-05 13:40:58 +02:00
parent 78b4bbcd49
commit bc0b18ab7c
Signed by: muellerr
GPG Key ID: A649FB78196E3849
6 changed files with 303 additions and 61 deletions

View File

@ -26,7 +26,6 @@ incremental = false
lto = 'fat' lto = 'fat'
opt-level = 3 # <- opt-level = 3 # <-
overflow-checks = false # <- overflow-checks = false # <-
strip = true
[profile.small] [profile.small]
inherits = "release" inherits = "release"
@ -35,5 +34,3 @@ debug-assertions = false # <-
lto = true lto = true
opt-level = 'z' # <- opt-level = 'z' # <-
overflow-checks = false # <- overflow-checks = false # <-
strip = true

View File

@ -114,6 +114,7 @@ fn main() -> ! {
if DEBUG_PRINTOUTS { if DEBUG_PRINTOUTS {
rprintln!("both images corrupt! booting image A"); rprintln!("both images corrupt! booting image A");
} }
loop {}
// TODO: Shift a CCSDS packet out to inform host/OBC about image corruption. // TODO: Shift a CCSDS packet out to inform host/OBC about image corruption.
// Both images seem to be corrupt. Boot default image A. // Both images seem to be corrupt. Boot default image A.
boot_app(AppSel::A, &cp) boot_app(AppSel::A, &cp)

View File

@ -7,9 +7,27 @@ edition = "2021"
cortex-m = "0.7" cortex-m = "0.7"
cortex-m-rt = "0.7" cortex-m-rt = "0.7"
embedded-hal = "1" embedded-hal = "1"
embedded-hal-nb = "1"
panic-rtt-target = { version = "0.1.3" } panic-rtt-target = { version = "0.1.3" }
rtt-target = { version = "0.5" } rtt-target = { version = "0.5" }
rtt-log = "0.3"
log = "0.4"
crc = "3" 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] [dependencies.va416xx-hal]
path = "../va416xx-hal" path = "../va416xx-hal"

9
flashloader/src/lib.rs Normal file
View File

@ -0,0 +1,9 @@
#![no_std]
#[cfg(test)]
mod tests {
#[test]
fn simple() {
assert_eq!(1 + 1, 2);
}
}

View File

@ -18,48 +18,18 @@
#![no_main] #![no_main]
#![no_std] #![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 panic_rtt_target as _;
use va416xx_hal::{ use va416xx_hal::{clock::Clocks, edac, pac, time::Hertz, wdt::Wdt};
edac,
pac::{self},
time::Hertz,
wdt::Wdt,
};
const EXTCLK_FREQ: u32 = 40_000_000; const EXTCLK_FREQ: u32 = 40_000_000;
const WITH_WDT: bool = true; const COBS_FRAME_SEPARATOR: u8 = 0x0;
const WDT_FREQ_MS: u32 = 50; const MAX_PACKET_SIZE: usize = 1024;
const DEBUG_PRINTOUTS: bool = true; 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 BOOT_NVM_MEMORY_ID: u8 = 1;
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<u16> = Crc::<u16>::new(&CRC_16_IBM_3740);
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
enum AppSel {
A,
B,
}
pub trait WdtInterface { pub trait WdtInterface {
fn feed(&self); fn feed(&self);
@ -75,25 +45,64 @@ impl WdtInterface for OptWdt {
} }
} }
#[rtic::app(device = pac)] static CLOCKS: OnceCell<Clocks> = OnceCell::new();
#[rtic::app(device = pac, dispatchers = [U1, U2, U3])]
mod app { mod app {
use super::*; use super::*;
use embedded_hal_nb::nb;
use panic_rtt_target as _; use panic_rtt_target as _;
use rtic_monotonics::systick::ExtU32;
use rtic_monotonics::systick::Systick; use rtic_monotonics::systick::Systick;
use rtt_target::{rprintln, rtt_init_default}; use rtic_sync::{
use va416xx_hal::{clock::ClkgenExt, edac, pac}; 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}; use crate::{setup_edac, EXTCLK_FREQ};
#[derive(Default, Debug, Copy, Clone, PartialEq, Eq)]
pub enum CobsReaderStates {
#[default]
WaitingForStart,
WatingForEnd,
FrameOverflow,
}
#[local] #[local]
struct Local {} struct Local {
uart_rx: uart::Rx<pac::Uart0>,
uart_tx: uart::Tx<pac::Uart0>,
cobs_reader_state: CobsReaderStates,
tc_tx: TcTx,
tc_rx: TcRx,
rom_spi: Option<pac::Spi3>,
}
#[shared] #[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] #[init]
fn init(mut cx: init::Context) -> (Shared, Local) { fn init(mut cx: init::Context) -> (Shared, Local) {
rtt_init_default!(); //rtt_init_default!();
rtt_log::init();
rprintln!("-- Vorago flashloader --"); rprintln!("-- Vorago flashloader --");
// Initialize the systick interrupt & obtain the token to prove that we did // Initialize the systick interrupt & obtain the token to prove that we did
let systick_mono_token = rtic_monotonics::create_systick_token!(); let systick_mono_token = rtic_monotonics::create_systick_token!();
@ -107,8 +116,36 @@ mod app {
.unwrap(); .unwrap();
setup_edac(&mut cx.device.sysconfig); 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); 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 // `shared` cannot be accessed from this context
@ -118,6 +155,169 @@ mod app {
loop {} 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)] #[task(binds = EDAC_SBE, priority = 1)]
fn edac_sbe_isr(_cx: edac_sbe_isr::Context) { fn edac_sbe_isr(_cx: edac_sbe_isr::Context) {
// TODO: Send some command via UART for notification purposes. Also identify the problematic // TODO: Send some command via UART for notification purposes. Also identify the problematic

View File

@ -44,7 +44,7 @@ pub const BP_0_ENABLE_MASK: u8 = 1 << 2;
pub const BP_1_ENABLE_MASK: u8 = 1 << 3; pub const BP_1_ENABLE_MASK: u8 = 1 << 3;
pub struct Nvm { pub struct Nvm {
spi: pac::Spi3, spi: Option<pac::Spi3>,
} }
#[derive(Debug, PartialEq, Eq)] #[derive(Debug, PartialEq, Eq)]
@ -90,7 +90,7 @@ impl Nvm {
// programmers guide // programmers guide
spi.ctrl1().modify(|_, w| w.enable().set_bit()); 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.disable_write_prot();
nvm nvm
} }
@ -110,36 +110,40 @@ impl Nvm {
self.write_single(FRAM_WRSR); self.write_single(FRAM_WRSR);
self.write_with_bmstop(0x00); self.write_with_bmstop(0x00);
} }
#[inline(always)]
pub fn spi(&self) -> &pac::Spi3 {
self.spi.as_ref().unwrap()
}
#[inline(always)] #[inline(always)]
pub fn write_single(&self, word: u8) { 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)] #[inline(always)]
pub fn write_with_bmstop(&self, word: u8) { pub fn write_with_bmstop(&self, word: u8) {
self.spi self.spi()
.data() .data()
.write(|w| unsafe { w.bits(BMSTART_BMSTOP_MASK | word as u32) }) .write(|w| unsafe { w.bits(BMSTART_BMSTOP_MASK | word as u32) })
} }
#[inline(always)] #[inline(always)]
pub fn wait_for_tx_idle(&self) { 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(); cortex_m::asm::nop();
} }
} }
#[inline(always)] #[inline(always)]
pub fn wait_for_rx_available(&self) { 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(); cortex_m::asm::nop();
} }
} }
#[inline(always)] #[inline(always)]
pub fn read_single_word(&self) -> u32 { 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]) { 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(mid_addr_byte(addr));
self.write_single(lsb_addr_byte(addr)); self.write_single(lsb_addr_byte(addr));
for byte in data.iter().take(data.len() - 1) { 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(); cortex_m::asm::nop();
} }
self.write_single(*byte); self.write_single(*byte);
self.read_single_word(); 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(); cortex_m::asm::nop();
} }
self.write_with_bmstop(*data.last().unwrap()); self.write_with_bmstop(*data.last().unwrap());
@ -194,13 +198,19 @@ impl Nvm {
Ok(()) Ok(())
} }
/// This function releases the ROM SPI and enables chip write protection again. /// Enable write-protection and disables the peripheral clock.
pub fn release(self) -> pac::Spi3 { pub fn shutdown(&mut self, sys_cfg: &mut pac::Sysconfig) {
self.wait_for_tx_idle(); self.wait_for_tx_idle();
self.write_with_bmstop(FRAM_WREN); self.write_with_bmstop(FRAM_WREN);
self.wait_for_tx_idle(); self.wait_for_tx_idle();
self.write_single(WPEN_ENABLE_MASK | BP_0_ENABLE_MASK | BP_1_ENABLE_MASK); 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) { 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() });
}
}