stupid nvm
This commit is contained in:
parent
78b4bbcd49
commit
bc0b18ab7c
@ -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
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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"
|
||||
|
9
flashloader/src/lib.rs
Normal file
9
flashloader/src/lib.rs
Normal file
@ -0,0 +1,9 @@
|
||||
#![no_std]
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#[test]
|
||||
fn simple() {
|
||||
assert_eq!(1 + 1, 2);
|
||||
}
|
||||
}
|
@ -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<u16> = Crc::<u16>::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<Clocks> = 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<pac::Uart0>,
|
||||
uart_tx: uart::Tx<pac::Uart0>,
|
||||
cobs_reader_state: CobsReaderStates,
|
||||
tc_tx: TcTx,
|
||||
tc_rx: TcRx,
|
||||
rom_spi: Option<pac::Spi3>,
|
||||
}
|
||||
|
||||
#[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
|
||||
|
@ -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<pac::Spi3>,
|
||||
}
|
||||
|
||||
#[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() });
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user