start updating the ancient flash loader
shared-hal-ci / Check build (push) Waiting to run
shared-hal-ci / Check formatting (push) Waiting to run
shared-hal-ci / Check Documentation Build (push) Waiting to run
shared-hal-ci / Clippy (push) Waiting to run
va108xx-ci / Check build (push) Waiting to run
va108xx-ci / Run Tests (push) Waiting to run
va108xx-ci / Check formatting (push) Waiting to run
va108xx-ci / Check Documentation Build (push) Waiting to run
va108xx-ci / Clippy (push) Waiting to run
va416xx-ci / Check build (push) Waiting to run
va416xx-ci / Run Tests (push) Waiting to run
va416xx-ci / Check formatting (push) Waiting to run
va416xx-ci / Check Documentation Build (push) Waiting to run
va416xx-ci / Clippy (push) Waiting to run
shared-hal-ci / Check build (pull_request) Waiting to run
shared-hal-ci / Check formatting (pull_request) Waiting to run
shared-hal-ci / Check Documentation Build (pull_request) Waiting to run
shared-hal-ci / Clippy (pull_request) Waiting to run
va108xx-ci / Check build (pull_request) Waiting to run
va108xx-ci / Run Tests (pull_request) Waiting to run
va108xx-ci / Check formatting (pull_request) Waiting to run
va108xx-ci / Check Documentation Build (pull_request) Waiting to run
va108xx-ci / Clippy (pull_request) Waiting to run
va416xx-ci / Check build (pull_request) Waiting to run
va416xx-ci / Run Tests (pull_request) Waiting to run
va416xx-ci / Check formatting (pull_request) Waiting to run
va416xx-ci / Check Documentation Build (pull_request) Waiting to run
va416xx-ci / Clippy (pull_request) Waiting to run

This commit is contained in:
Robin Mueller
2026-05-19 12:13:46 +02:00
parent 28c5628163
commit 44a1f27431
20 changed files with 829 additions and 385 deletions
+1 -2
View File
@@ -22,13 +22,12 @@ check-va108xx:
[working-directory: 'va416xx']
check-va416xx:
cargo check --target thumbv7em-none-eabihf
cargo check --target thumbv7em-none-eabihf --examples
cargo check -p va416xx --target thumbv7em-none-eabihf --all-features
cargo check -p va416xx-hal --target thumbv7em-none-eabihf --features "defmt va41630"
[working-directory: 'va108xx']
build-va108xx:
cargo build --target thumbv6m-none-eabi
cargo build --target thumbv6m-none-eabi --release
[working-directory: 'va416xx']
build-va416xx:
@@ -0,0 +1 @@
output.log
@@ -0,0 +1,22 @@
[package]
name = "client"
version = "0.1.0"
edition = "2024"
[dependencies]
clap = { version = "4", features = ["derive"] }
fern = "0.7"
object = "0.39"
humantime = "2"
crc = "3"
log = "0.4"
toml = "1.1.2+spec-1.1.0"
anyhow = "1"
serialport = "4"
cobs = "0.5"
serde = { version = "1", features = ["derive"] }
models = { path = "../../va108xx/flashloader/models" }
postcard = { version = "1", features = ["alloc"] }
spacepackets = { version = "0.17" }
# tmtc-utils = { git = "https://egit.irs.uni-stuttgart.de/rust/tmtc-utils.git", version = "0.1" }
tmtc-utils = { path = "../../../../ROMEO/tmtc-utils", version = "0.1" }
@@ -0,0 +1,2 @@
[interface]
serial_port = "/dev/ttyUSB0"
@@ -0,0 +1,92 @@
use std::path::Path;
use models::{
APP_A_START_ADDR, APP_A_MAX_SIZE, APP_B_START_ADDR, APP_B_MAX_SIZE,
BOOTLOADER_START_ADDR, BOOTLOADER_MAX_SIZE,
};
use crate::Target;
pub struct LoadableSegment {
pub name: String,
pub offset: u32,
pub data: Vec<u8>,
}
pub fn parse_elf(target: &Target, path: &Path) -> anyhow::Result<Vec<LoadableSegment>> {
use object::{Object, ObjectSection, ObjectSegment};
let raw = std::fs::read(path)?;
let obj = object::File::parse(raw.as_slice())?;
let expected_base: u32 = match target {
Target::Bl => BOOTLOADER_START_ADDR,
Target::A => APP_A_START_ADDR,
Target::B => APP_B_START_ADDR,
};
let mut segments = Vec::new();
for (idx, segment) in obj.segments().enumerate() {
let data = segment.data()?;
if data.is_empty() {
continue;
}
let addr = segment.address() as u32;
if idx == 0 && addr != expected_base {
anyhow::bail!(
"unexpected base address {addr:#010x} for {target:?}, expected {expected_base:#010x}"
);
}
let name = obj
.sections()
.find(|s| {
let s_addr = s.address() as u32;
s_addr >= addr
&& s_addr < addr + data.len() as u32
&& !s.name().unwrap_or("").is_empty()
})
.and_then(|s| s.name().ok().map(str::to_owned))
.unwrap_or_else(|| format!("<segment {idx}>"));
segments.push(LoadableSegment {
name,
offset: addr,
data: data.to_vec(),
});
}
Ok(segments)
}
pub fn check_total_size(target: &Target, total_size: usize) -> anyhow::Result<()> {
let max = match target {
Target::Bl => BOOTLOADER_MAX_SIZE,
Target::A => APP_A_MAX_SIZE,
Target::B => APP_B_MAX_SIZE,
} as usize;
if total_size > max {
anyhow::bail!(
"{target:?} image is {total_size} bytes, exceeds maximum of {max} bytes"
);
}
Ok(())
}
pub fn log_segments_info(target: &Target, segments: &[LoadableSegment], total_size: usize, path: &Path) {
log::info!(
"Flashing {target:?} with image '{}' — {} segment(s), {total_size} bytes total",
path.display(),
segments.len()
);
for (i, seg) in segments.iter().enumerate() {
log::info!(
" segment {i}: '{}' @ {:#010x}, {} bytes",
seg.name, seg.offset, seg.data.len()
);
}
}
@@ -0,0 +1,142 @@
use std::path::Path;
use anyhow::Context;
use crc::{CRC_16_IBM_3740, Crc};
use spacepackets::CcsdsPacketReader;
use tmtc_utils::transport::serial::PacketTransportSerialCobs;
use crate::{
Target,
elf::{check_total_size, log_segments_info, parse_elf},
tc,
};
use models::{
APP_A_CRC_ADDR, APP_A_SIZE_ADDR, APP_B_CRC_ADDR, APP_B_SIZE_ADDR, BOOTLOADER_CRC_ADDR,
};
use models::{Request, Response};
pub const CHUNK_SIZE: usize = 256;
fn send_and_await_ok(
transport: &mut PacketTransportSerialCobs,
request: &Request,
payload: &[u8],
timeout: std::time::Duration,
) -> anyhow::Result<()> {
let tc = tc::create_tc_with_additional_payload(request, payload);
log::debug!(
"TX TC {:#010x}: {request:?} + {} payload bytes",
tc.ccsds_packet_id_and_psc().raw(),
payload.len()
);
transport.send(&tc.to_vec())?;
let deadline = std::time::Instant::now() + timeout;
let mut got_ok = false;
while !got_ok {
if std::time::Instant::now() > deadline {
anyhow::bail!("timeout waiting for Ok response to {request:?}");
}
transport.receive(|packet| {
let Ok(reader) = CcsdsPacketReader::new_with_checksum(packet) else {
return;
};
match postcard::take_from_bytes::<Response>(reader.packet_data()) {
Ok((Response::Ok, _)) => got_ok = true,
#[allow(unreachable_patterns)]
Ok((other, _)) => log::warn!("unexpected response: {other:?}"),
Err(e) => log::error!("failed to deserialise response: {e}"),
}
})?;
}
Ok(())
}
pub fn flash_image(
transport: &mut PacketTransportSerialCobs,
target: &Target,
path: &Path,
) -> anyhow::Result<()> {
let segments = parse_elf(target, path)
.with_context(|| format!("failed to parse ELF: {}", path.display()))?;
let total_size: usize = segments.iter().map(|s| s.data.len()).sum();
check_total_size(target, total_size)?;
log_segments_info(target, &segments, total_size, path);
let ack_timeout = std::time::Duration::from_secs(2);
// 1. Write all segments in chunks, waiting for Ok after each.
for seg in &segments {
let mut pos = 0usize;
while pos < seg.data.len() {
let chunk_len = CHUNK_SIZE.min(seg.data.len() - pos);
let offset = seg.offset + pos as u32;
let chunk = &seg.data[pos..pos + chunk_len];
log::info!(
"Writing {chunk_len} bytes @ {offset:#010x} ({}/{} of '{}')",
pos + chunk_len,
seg.data.len(),
seg.name,
);
send_and_await_ok(transport, &Request::WriteNvm { offset }, chunk, ack_timeout)?;
pos += chunk_len;
}
}
// 2. CRC + size postprocessing.
match target {
Target::Bl => {
log::info!("Blanking bootloader CRC @ {BOOTLOADER_CRC_ADDR:#010x}");
send_and_await_ok(
transport,
&Request::WriteNvm {
offset: BOOTLOADER_CRC_ADDR,
},
&[0x00, 0x00],
ack_timeout,
)?;
}
Target::A | Target::B => {
let (size_addr, crc_addr) = match target {
Target::A => (APP_A_SIZE_ADDR, APP_A_CRC_ADDR),
Target::B => (APP_B_SIZE_ADDR, APP_B_CRC_ADDR),
Target::Bl => unreachable!(),
};
log::info!("Writing app size {total_size} @ {size_addr:#010x}");
send_and_await_ok(
transport,
&Request::WriteNvm { offset: size_addr },
&(total_size as u32).to_be_bytes(),
ack_timeout,
)?;
let crc = Crc::<u16>::new(&CRC_16_IBM_3740);
let mut digest = crc.digest();
for seg in &segments {
digest.update(&seg.data);
}
let checksum = digest.finalize().to_be_bytes();
log::info!(
"Writing CRC [{:#04x}, {:#04x}] @ {crc_addr:#010x}",
checksum[0],
checksum[1]
);
send_and_await_ok(
transport,
&Request::WriteNvm { offset: crc_addr },
&checksum,
ack_timeout,
)?;
}
}
log::info!("Flash complete.");
Ok(())
}
@@ -0,0 +1,194 @@
use std::{
fs::File,
io::Read as _,
net::SocketAddr,
path::{Path, PathBuf},
time::SystemTime,
};
use anyhow::bail;
use clap::Parser as _;
use cobs::CobsDecoderOwned;
use models::Response;
use spacepackets::CcsdsPacketReader;
use tmtc_utils::transport::serial::PacketTransportSerialCobs;
pub mod elf;
pub mod flash;
pub mod tc;
#[derive(clap::Parser)]
#[command(name = "image-loader", about = "VA416XX Image Loader Application")]
pub struct Cli {
/// Serial port to use (overrides loader.toml)
#[arg(short, long)]
pub port: Option<String>,
#[command(subcommand)]
pub command: Command,
}
#[derive(clap::Subcommand)]
pub enum Command {
/// Send a ping command
Ping,
/// Set the active boot slot
SetBootSlot {
/// Boot slot to activate
app: AppTarget,
},
/// Corrupt an app slot (for testing)
Corrupt {
/// Target slot to corrupt
app: AppTarget,
},
/// Flash an ELF image to a target slot
Flash {
/// Target to flash
target: Target,
/// Path to the ELF image
path: PathBuf,
},
}
#[derive(clap::ValueEnum, Clone, Debug)]
pub enum Target {
/// Bootloader slot
Bl,
/// Application slot A
A,
/// Application slot B
B,
}
/// Only app slots can be corrupted (not the bootloader)
#[derive(clap::ValueEnum, Clone, Debug)]
pub enum AppTarget {
A,
B,
}
#[derive(Debug, serde::Deserialize)]
pub struct Config {
pub interface: Interface,
}
#[derive(Debug, serde::Deserialize)]
pub struct Interface {
pub serial_port: Option<String>,
pub udp_addr: Option<SocketAddr>,
}
impl Config {
pub fn new_from_file() -> Self {
let mut config_file =
File::open(Path::new("config.toml")).expect("opening config.toml file failed");
let mut toml_str = String::new();
config_file
.read_to_string(&mut toml_str)
.expect("reading config.toml file failed");
let config: Config = toml::from_str(&toml_str).expect("parsing config.toml file failed");
config
}
}
pub fn setup_logger() -> Result<(), fern::InitError> {
fern::Dispatch::new()
.format(|out, message, record| {
out.finish(format_args!(
"[{} {} {}] {}",
humantime::format_rfc3339_seconds(SystemTime::now()),
record.level(),
record.target(),
message
))
})
.level(log::LevelFilter::Info)
.chain(std::io::stdout())
.chain(fern::log_file("output.log")?)
.apply()?;
Ok(())
}
fn main() -> anyhow::Result<()> {
setup_logger().expect("failed to initialize logger");
println!("-- VA108xx Flashloader Client --");
let cli = Cli::parse();
let config = Config::new_from_file();
if config.interface.serial_port.is_none() {
bail!("Serial port not specified in configuration file.");
}
let serial_port = config.interface.serial_port.as_ref().unwrap();
let serial = serialport::new(serial_port, 115200)
.open()
.expect("opening serial port failed");
let mut transport = PacketTransportSerialCobs::new(serial, CobsDecoderOwned::new(4096));
match cli.command {
Command::Ping => {
let tc = tc::create_tc(&models::Request::Ping);
log::info!(
"Sending ping request with TC ID: {:#010x}",
tc.ccsds_packet_id_and_psc().raw()
);
let tc_raw = tc.to_vec();
transport.send(&tc_raw).unwrap();
}
Command::SetBootSlot { app } => {
let app_sel = match app {
AppTarget::A => models::AppSel::A,
AppTarget::B => models::AppSel::B,
};
let tc = tc::create_tc(&models::Request::SetBootSlot(app_sel));
log::info!(
"Sending app select {:?} command with TC ID: {:#010x}",
app_sel,
tc.ccsds_packet_id_and_psc().raw()
);
transport.send(&tc.to_vec()).unwrap();
}
Command::Corrupt { app } => {
let app_sel = match app {
AppTarget::A => models::AppSel::A,
AppTarget::B => models::AppSel::B,
};
let tc = tc::create_tc(&models::Request::SetBootSlot(app_sel));
log::info!(
"Sending corrupt slot {:?} command with TC ID: {:#010x}",
app_sel,
tc.ccsds_packet_id_and_psc().raw()
);
transport.send(&tc.to_vec()).unwrap();
}
Command::Flash { target, path } => {
flash::flash_image(&mut transport, &target, &path)?;
}
}
log::info!("Waiting for response...");
loop {
transport
.receive(|packet: &[u8]| {
let reader = CcsdsPacketReader::new_with_checksum(packet);
log::debug!("Received packet: {:?}", reader);
if let Ok(reader) = reader {
let packet_data = reader.packet_data();
let response_result = postcard::take_from_bytes::<Response>(packet_data);
if let Ok((response, _remainder)) = response_result {
log::info!("Received TM with response: {:?}", response);
} else {
log::error!(
"Failed to parse response from packet data: {:?}",
response_result
);
}
}
})
.unwrap();
}
}
@@ -0,0 +1,14 @@
use spacepackets::{CcsdsPacketCreatorOwned, SpHeader};
pub fn create_tc(request: &models::Request) -> CcsdsPacketCreatorOwned {
let req_raw = postcard::to_allocvec(&request).unwrap();
let sp_header = SpHeader::new_from_apid(models::APID);
CcsdsPacketCreatorOwned::new_tc_with_checksum(sp_header, &req_raw).unwrap()
}
pub fn create_tc_with_additional_payload(request: &models::Request, payload: &[u8]) -> CcsdsPacketCreatorOwned {
let mut req_raw = postcard::to_allocvec(&request).unwrap();
req_raw.extend_from_slice(payload);
let sp_header = SpHeader::new_from_apid(models::APID);
CcsdsPacketCreatorOwned::new_tc_with_checksum(sp_header, &req_raw).unwrap()
}
+5 -1
View File
@@ -1,5 +1,5 @@
[workspace]
resolver = "2"
resolver = "3"
members = [
"vorago-reb1",
"va108xx",
@@ -10,6 +10,7 @@ members = [
"board-tests",
"bootloader",
"flashloader",
"flashloader/models",
]
exclude = [
"flashloader/slot-a-blinky",
@@ -43,3 +44,6 @@ lto = true
opt-level = 'z' # <-
overflow-checks = false # <-
strip = true # Automatically strip symbols from the binary.
[patch.crates-io]
spacepackets = { git = "https://egit.irs.uni-stuttgart.de/rust/spacepackets.git", rev = "ec952b771cc62a371786cb1a04fd1baa9c33523c" }
@@ -67,7 +67,7 @@ mod app {
InterruptConfig::new(pac::Interrupt::OC3, true, true),
);
let (tx, rx) = irq_uart.split();
let mut rx = rx.into_rx_with_irq();
let mut rx = rx.into_rx_with_interrupt();
rx.start();
+10 -6
View File
@@ -11,23 +11,27 @@ defmt = "1"
defmt-rtt = { version = "1" }
panic-probe = { version = "1", features = ["print-defmt"] }
num_enum = { version = "0.7", default-features = false }
cobs = { version = "0.5", default-features = false }
satrs = { version = "0.3.0-alpha.3", default-features = false, features = ["defmt"] }
cobs = { version = "0.5", default-features = false, features = ["defmt"] }
fugit = "0.4"
arbitrary-int = "2"
ringbuf = { version = "0.4.7", default-features = false, features = ["portable-atomic"] }
# spacepackets = { version = "0.17", path = "https://egit.irs.uni-stuttgart.de/rust/spacepackets.git", default-features = false, features = ["defmt"] }
embassy-sync = "0.8"
embedded-io-async = "0.7"
embassy-time = { version = "0.5", features = ["defmt-timestamp-uptime-ms"] }
static_cell = "2"
postcard = { version = "1", features = ["use-defmt"] }
spacepackets = { version = "0.17", default-features = false, features = ["defmt"] }
serde = { version = "1", default-features = false }
# Even though we do not use this directly, we need to activate this feature explicitely
# so that RTIC compiles because thumv6 does not have CAS operations natively.
portable-atomic = {version = "1", features = ["unsafe-assume-single-core"]}
models = { path = "./models", features = ["defmt"] }
rtic = { version = "2", features = ["thumbv6-backend"] }
rtic-monotonics = { version = "2", features = ["cortex-m-systick"] }
[dependencies.va108xx-hal]
version = "0.13"
path = "../va108xx-hal"
features = ["defmt"]
features = ["defmt", "embassy-oc30-oc31"]
[dependencies.vorago-reb1]
version = "0.10"
+4 -4
View File
@@ -2,9 +2,9 @@ VA108xx Flashloader Application
========
This flashloader shows a minimal example for a self-updatable Rust software which exposes
a simple PUS (CCSDS) interface to update the software. It also provides a Python application
called the `image-loader.py` which can be used to upload compiled images to the flashloader
application to write them to the NVM.
a simple PUS (CCSDS) interface to update the software. It also provides a Rust application
which can be used to upload compiled images to the flashloader application to write them to the NVM.
You can find it inside the `tools/va108xx-image-loader` directory.
Please note that the both the application and the image loader are tailored towards usage
with the [bootloader provided by this repository](https://egit.irs.uni-stuttgart.de/rust/vorago-rs/src/branch/main/va108xx/bootloader).
@@ -13,7 +13,7 @@ The software can quickly be adapted to interface with a real primary on-board so
the Python script provided here to upload images because it uses a low-level CCSDS based packet
interface.
## Using the Python image loader
## Using the image loader
The Python image loader communicates with the Rust flashload application using a dedicated serial
port with a baudrate of 115200.
+14
View File
@@ -0,0 +1,14 @@
[package]
name = "models"
version = "0.1.0"
edition = "2024"
[dependencies]
postcard = { version = "1" }
spacepackets = { version = "0.17", default-features = false, features = ["defmt"] }
num_enum = { version = "0.7", default-features = false }
defmt = { version = "1", optional = true }
serde = { version = "1", default-features = false, features = ["derive"] }
cobs = { version = "0.5", default-features = false }
thiserror = { version = "2", default-features = false }
arbitrary-int = "2"
+98
View File
@@ -0,0 +1,98 @@
#![no_std]
use arbitrary_int::u11;
use cobs::DestBufTooSmallError;
use spacepackets::{
CcsdsPacketCreationError, CcsdsPacketCreatorWithReservedData, SpacePacketHeader,
};
pub const APID: u11 = u11::new(0x01);
pub const BOOTLOADER_START_ADDR: u32 = 0x0000_0000;
pub const BOOTLOADER_END_ADDR: u32 = 0x0000_3000;
pub const BOOTLOADER_CRC_ADDR: u32 = BOOTLOADER_END_ADDR - 2;
pub const BOOTLOADER_MAX_SIZE: u32 = BOOTLOADER_END_ADDR - BOOTLOADER_START_ADDR - 2;
pub const APP_A_START_ADDR: u32 = 0x0000_3000;
pub const APP_B_END_ADDR: u32 = 0x0002_0000 - 8;
pub const IMG_SLOT_SIZE: u32 = (APP_B_END_ADDR - APP_A_START_ADDR) / 2;
pub const APP_A_END_ADDR: u32 = APP_A_START_ADDR + IMG_SLOT_SIZE;
pub const APP_A_SIZE_ADDR: u32 = APP_A_END_ADDR - 8;
pub const APP_A_CRC_ADDR: u32 = APP_A_END_ADDR - 4;
pub const APP_A_MAX_SIZE: u32 = IMG_SLOT_SIZE - 8;
pub const APP_B_START_ADDR: u32 = APP_A_END_ADDR;
pub const APP_B_SIZE_ADDR: u32 = APP_B_END_ADDR - 8;
pub const APP_B_CRC_ADDR: u32 = APP_B_END_ADDR - 4;
pub const APP_B_MAX_SIZE: u32 = IMG_SLOT_SIZE - 8;
#[derive(
Debug,
Copy,
Clone,
PartialEq,
Eq,
num_enum::TryFromPrimitive,
serde::Serialize,
serde::Deserialize,
)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[repr(u8)]
pub enum AppSel {
A = 0,
B = 1,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum Request {
Ping,
Corrupt(AppSel),
WriteNvm { offset: u32 },
SetBootSlot(AppSel),
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum Response {
Ok,
}
pub fn create_tm_packet(
buf: &mut [u8],
sp_header: SpacePacketHeader,
response: Response,
) -> Result<usize, CcsdsPacketCreationError> {
let packet_data_size = postcard::experimental::serialized_size(&response).unwrap();
let mut creator =
CcsdsPacketCreatorWithReservedData::new_tm_with_checksum(sp_header, packet_data_size, buf)?;
let current_index = 0;
postcard::to_slice(&response, &mut creator.packet_data_mut()[current_index..]).unwrap();
Ok(creator.finish())
}
#[derive(Debug, thiserror::Error)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum PacketCreationAndEncodingError {
#[error("packet creation failed: {0}")]
Creation(#[from] CcsdsPacketCreationError),
#[error("destination buffer too small: {0}")]
Encoding(#[from] DestBufTooSmallError),
}
pub fn create_encoded_tm_packet(
buf: &mut [u8],
encoded_buf: &mut [u8],
sp_header: SpacePacketHeader,
response: Response,
) -> Result<usize, PacketCreationAndEncodingError> {
let packet_len = create_tm_packet(buf, sp_header, response)?;
let encoded_len =
cobs::try_encode_including_sentinels(&buf[0..packet_len], &mut encoded_buf[..])?;
Ok(encoded_len)
}
#[cfg(test)]
mod tests {}
+165 -331
View File
@@ -4,51 +4,22 @@
#![no_std]
use defmt_rtt as _; // global logger
use num_enum::TryFromPrimitive;
use panic_probe as _;
use ringbuf::{
traits::{Consumer, Observer, Producer},
StaticRb,
};
use rtic_monotonics::fugit::ExtU32;
use va108xx_hal::time::Hertz;
const SYSCLK_FREQ: Hertz = Hertz::from_raw(50_000_000);
const UART_BANK: va108xx_hal::uart::Bank = va108xx_hal::uart::Bank::Uart0;
const MAX_TC_SIZE: usize = 524;
const MAX_TC_FRAME_SIZE: usize = cobs::max_encoding_length(MAX_TC_SIZE);
pub const MAX_TC_SIZE: usize = 524;
pub const MAX_TC_FRAME_SIZE: usize = cobs::max_encoding_length(MAX_TC_SIZE);
const MAX_TM_SIZE: usize = 128;
const MAX_TM_FRAME_SIZE: usize = cobs::max_encoding_length(MAX_TM_SIZE);
const UART_BAUDRATE: u32 = 115200;
const BOOT_NVM_MEMORY_ID: u8 = 1;
const RX_DEBUGGING: bool = false;
pub enum ActionId {
CorruptImageA = 128,
CorruptImageB = 129,
SetBootSlot = 130,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, TryFromPrimitive, defmt::Format)]
#[repr(u8)]
enum AppSel {
A = 0,
B = 1,
}
// Larger buffer for TC to be able to hold the possibly large memory write packets.
const BUF_RB_SIZE_TC: usize = 1024;
const SIZES_RB_SIZE_TC: usize = 16;
const BUF_RB_SIZE_TM: usize = 256;
const SIZES_RB_SIZE_TM: usize = 16;
pub struct RingBufWrapper<const BUF_SIZE: usize, const SIZES_LEN: usize> {
pub buf: StaticRb<u8, BUF_SIZE>,
pub sizes: StaticRb<usize, SIZES_LEN>,
}
const TC_PIPE_SIZE: usize = 1024;
const TM_PIPE_SIZE: usize = 128;
pub const APP_A_START_ADDR: u32 = 0x3000;
pub const APP_A_END_ADDR: u32 = 0x117FC;
@@ -60,21 +31,16 @@ pub const PREFERRED_SLOT_OFFSET: u32 = 0x20000 - 1;
#[rtic::app(device = pac, dispatchers = [OC20, OC21, OC22])]
mod app {
use super::*;
use arbitrary_int::traits::Integer as _;
use arbitrary_int::{u11, u14};
use cobs::CobsDecoderHeapless;
use cortex_m::asm;
use embedded_io::Write;
use rtic::Mutex;
use rtic_monotonics::Monotonic;
use satrs::pus::verification::{FailParams, VerificationReportCreator};
use satrs::spacepackets::ecss::PusServiceId;
use satrs::spacepackets::ecss::{
tc::PusTcReader, tm::PusTmCreator, EcssEnumU8, PusPacket, WritablePusPacket,
};
use embassy_sync::blocking_mutex::raw::{CriticalSectionRawMutex, NoopRawMutex};
use embedded_io_async::Write as _;
use models::{create_encoded_tm_packet, Response};
use spacepackets::{CcsdsPacketReader, SpacePacketHeader};
use va108xx_hal::pins::PinsA;
use va108xx_hal::spi::SpiClockConfig;
use va108xx_hal::uart::InterruptContextTimeoutOrMaxSize;
use va108xx_hal::{pac, uart, InterruptConfig};
use va108xx_hal::uart::{self, TxAsync};
use va108xx_hal::{pac, InterruptConfig};
use vorago_reb1::m95m01::M95M01;
#[derive(Default, Debug, Copy, Clone, PartialEq, Eq)]
@@ -88,32 +54,29 @@ mod app {
#[local]
struct Local {
uart_rx: uart::RxWithInterrupt,
uart_tx: uart::Tx,
rx_context: InterruptContextTimeoutOrMaxSize,
verif_reporter: VerificationReportCreator,
uart_tx: uart::TxAsync,
nvm: M95M01,
tc_tx: embassy_sync::pipe::Writer<'static, CriticalSectionRawMutex, TC_PIPE_SIZE>,
tc_rx: embassy_sync::pipe::Reader<'static, CriticalSectionRawMutex, TC_PIPE_SIZE>,
// We are only using this in threads, and RTIC ensures there are no conflicts.
tm_tx: embassy_sync::pipe::Writer<'static, NoopRawMutex, TM_PIPE_SIZE>,
tm_rx: embassy_sync::pipe::Reader<'static, NoopRawMutex, TM_PIPE_SIZE>,
}
#[shared]
struct Shared {
// Having this shared allows multiple tasks to generate telemetry.
tm_rb: RingBufWrapper<BUF_RB_SIZE_TM, SIZES_RB_SIZE_TM>,
tc_rb: RingBufWrapper<BUF_RB_SIZE_TC, SIZES_RB_SIZE_TC>,
}
rtic_monotonics::systick_monotonic!(Mono, 1000);
struct Shared {}
#[init]
fn init(cx: init::Context) -> (Shared, Local) {
defmt::println!("-- Vorago flashloader --");
Mono::start(cx.core.SYST, SYSCLK_FREQ.to_raw());
let periphs = cx.device;
va108xx_hal::embassy_time::init(periphs.tim14, periphs.tim15, SYSCLK_FREQ);
let dp = cx.device;
let spi_clock_config = SpiClockConfig::new(2, 4);
let nvm = M95M01::new(dp.spic, spi_clock_config);
let nvm = M95M01::new(periphs.spic, spi_clock_config);
let gpioa = PinsA::new(dp.porta);
let gpioa = PinsA::new(periphs.porta);
let tx = gpioa.pa9;
let rx = gpioa.pa8;
@@ -124,7 +87,7 @@ mod app {
);
let uart_config = uart::Config::new_with_clock_config(clock_config);
let irq_uart = uart::Uart::new_with_interrupt_uart0(
dp.uarta,
periphs.uarta,
tx,
rx,
uart_config,
@@ -132,32 +95,32 @@ mod app {
);
let (tx, rx) = irq_uart.split();
// Unwrap is okay, we explicitely set the interrupt ID.
let mut rx = rx.into_rx_with_irq();
let mut rx = rx.into_rx_with_interrupt();
let verif_reporter = VerificationReportCreator::new(u11::new(0));
rx.start();
tc_handler::spawn().unwrap();
tm_tx_handler::spawn().unwrap();
let mut rx_context = InterruptContextTimeoutOrMaxSize::new(MAX_TC_FRAME_SIZE);
rx.read_fixed_len_or_timeout_based_using_irq(&mut rx_context)
.expect("initiating UART RX failed");
pus_tc_handler::spawn().unwrap();
pus_tm_tx_handler::spawn().unwrap();
let tx_async = TxAsync::new(tx);
static TC_PIPE: static_cell::ConstStaticCell<
embassy_sync::pipe::Pipe<CriticalSectionRawMutex, TC_PIPE_SIZE>,
> = static_cell::ConstStaticCell::new(embassy_sync::pipe::Pipe::new());
static TM_PIPE: static_cell::ConstStaticCell<
embassy_sync::pipe::Pipe<NoopRawMutex, TM_PIPE_SIZE>,
> = static_cell::ConstStaticCell::new(embassy_sync::pipe::Pipe::new());
let (tc_rx, tc_tx) = TC_PIPE.take().split();
let (tm_rx, tm_tx) = TM_PIPE.take().split();
(
Shared {
tc_rb: RingBufWrapper {
buf: StaticRb::default(),
sizes: StaticRb::default(),
},
tm_rb: RingBufWrapper {
buf: StaticRb::default(),
sizes: StaticRb::default(),
},
},
Shared {},
Local {
uart_rx: rx,
uart_tx: tx,
rx_context,
verif_reporter,
uart_tx: tx_async,
nvm,
tc_tx,
tc_rx,
tm_tx,
tm_rx,
},
)
}
@@ -174,285 +137,156 @@ mod app {
#[task(
binds = OC0,
local = [
cnt: u32 = 0,
rx_buf: [u8; MAX_TC_FRAME_SIZE] = [0; MAX_TC_FRAME_SIZE],
rx_context,
uart_rx,
tc_tx
],
shared = [tc_rb]
)]
fn uart_rx_irq(mut cx: uart_rx_irq::Context) {
match cx
.local
.uart_rx
.on_interrupt_max_size_or_timeout_based(cx.local.rx_context, cx.local.rx_buf)
{
Ok(result) => {
if RX_DEBUGGING {
defmt::debug!("RX Info: {:?}", cx.local.rx_context);
defmt::debug!("RX Result: {:?}", result);
fn uart_irq(cx: uart_irq::Context) {
let mut buf: [u8; 16] = [0; 16];
let result = cx.local.uart_rx.on_interrupt(&mut buf);
if result.bytes_read > 0 {
let mut written_so_far = 0;
while written_so_far < result.bytes_read {
let write_result = cx
.local
.tc_tx
.try_write(&buf[written_so_far..result.bytes_read]);
if write_result.is_err() {
defmt::warn!("TC pipe full, dropping bytes");
break;
}
if result.complete() {
// Check frame validity (must have COBS format) and decode the frame.
// Currently, we expect a full frame or a frame received through a timeout
// to be one COBS frame. We could parse for multiple COBS packets in one
// frame, but the additional complexity is not necessary here..
if cx.local.rx_buf[0] == 0 && cx.local.rx_buf[result.bytes_read - 1] == 0 {
let decoded_size =
cobs::decode_in_place(&mut cx.local.rx_buf[1..result.bytes_read]);
if decoded_size.is_err() {
defmt::warn!("COBS decoding failed");
} else {
let decoded_size = decoded_size.unwrap();
let mut tc_rb_full = false;
cx.shared.tc_rb.lock(|rb| {
if rb.sizes.vacant_len() >= 1 && rb.buf.vacant_len() >= decoded_size
{
rb.sizes.try_push(decoded_size).unwrap();
rb.buf.push_slice(&cx.local.rx_buf[1..1 + decoded_size]);
} else {
tc_rb_full = true;
}
});
if tc_rb_full {
defmt::warn!("COBS TC queue full");
}
}
} else {
defmt::warn!(
"COBS frame with invalid format, start and end bytes are not 0"
);
}
// Initiate next transfer.
cx.local
.uart_rx
.read_fixed_len_or_timeout_based_using_irq(cx.local.rx_context)
.expect("read operation failed");
}
if result.has_errors() {
defmt::warn!("UART error: {:?}", result.errors.unwrap());
}
}
Err(e) => {
defmt::warn!("UART error: {:?}", e);
written_so_far += write_result.unwrap();
}
}
va108xx_hal::uart::tx_async::on_interrupt_tx(UART_BANK);
}
#[task(
priority = 2,
local=[
tc_buf: [u8; MAX_TC_SIZE] = [0; MAX_TC_SIZE],
readback_buf: [u8; MAX_TC_SIZE] = [0; MAX_TC_SIZE],
src_data_buf: [u8; 16] = [0; 16],
verif_buf: [u8; 32] = [0; 32],
nvm,
verif_reporter
tc_rx,
tm_tx
],
shared=[tm_rb, tc_rb]
)]
async fn pus_tc_handler(mut cx: pus_tc_handler::Context) {
async fn tc_handler(cx: tc_handler::Context) {
let mut read_buf: [u8; 64] = [0; 64];
let mut tm_buf: [u8; MAX_TM_FRAME_SIZE] = [0; MAX_TM_FRAME_SIZE];
let mut encoded_tm_buf: [u8; MAX_TM_FRAME_SIZE] = [0; MAX_TM_FRAME_SIZE];
let mut cobs_decoder = CobsDecoderHeapless::<TC_PIPE_SIZE>::new();
loop {
// Try to read a TC from the ring buffer.
let packet_len = cx.shared.tc_rb.lock(|rb| rb.sizes.try_pop());
if packet_len.is_none() {
// Small delay, TCs might arrive very quickly.
Mono::delay(20_u32.millis()).await;
continue;
let read_bytes = cx.local.tc_rx.read(&mut read_buf).await;
for &byte in read_buf[0..read_bytes].iter() {
match cobs_decoder.feed(byte) {
Ok(result) => {
if result.is_none() {
continue;
}
let frame = result.unwrap();
match CcsdsPacketReader::new_with_checksum(&cobs_decoder.dest()[0..frame]) {
Ok(packet) => {
let request = postcard::take_from_bytes::<models::Request>(
packet.user_data(),
);
if request.is_err() {
defmt::warn!(
"Failed to parse command: {}",
request.err().unwrap()
);
continue;
}
let (request, remainder) = request.unwrap();
let response = match request {
models::Request::Corrupt(slot) => {
match slot {
models::AppSel::A => {
defmt::info!("corrupting App Image A");
corrupt_image(APP_A_START_ADDR, cx.local.nvm);
}
models::AppSel::B => {
defmt::info!("corrupting App Image B");
corrupt_image(APP_B_START_ADDR, cx.local.nvm);
}
}
Response::Ok
}
models::Request::WriteNvm { offset } => {
defmt::info!(
"writing {} bytes to NVM at offset 0x{:08x}",
remainder.len(),
offset
);
cx.local.nvm.write(offset as usize, remainder);
defmt::info!("write complete");
Response::Ok
}
models::Request::SetBootSlot(app_sel) => {
defmt::info!(
"received boot selection command with app select: {:?}",
app_sel
);
cx.local.nvm.write(
PREFERRED_SLOT_OFFSET as usize,
&[app_sel as u8],
);
Response::Ok
}
models::Request::Ping => {
defmt::info!("received ping TC");
Response::Ok
}
};
match create_encoded_tm_packet(
&mut tm_buf,
&mut encoded_tm_buf,
SpacePacketHeader::new_from_apid(models::APID),
response,
) {
Ok(encoded_len) => {
cx.local
.tm_tx
.write_all(&encoded_tm_buf[0..encoded_len])
.await;
}
Err(e) => {
defmt::warn!("Failed to create or encode TM packet: {}", e);
}
}
}
Err(e) => {
defmt::warn!("CCSDS packet error: {}", e);
}
}
}
Err(e) => {
defmt::warn!("COBS decoding error: {}", e);
}
}
}
let packet_len = packet_len.unwrap();
defmt::info!("received packet with length {}", packet_len);
let popped_packet_len = cx
.shared
.tc_rb
.lock(|rb| rb.buf.pop_slice(&mut cx.local.tc_buf[0..packet_len]));
assert_eq!(popped_packet_len, packet_len);
// Read a telecommand, now handle it.
handle_valid_pus_tc(&mut cx);
}
}
fn handle_valid_pus_tc(cx: &mut pus_tc_handler::Context) {
let pus_tc = PusTcReader::new(cx.local.tc_buf);
if let Err(e) = pus_tc {
defmt::warn!("PUS TC error: {}", e);
return;
}
let pus_tc = pus_tc.unwrap();
let mut write_and_send = |tm: &PusTmCreator| {
let written_size = tm.write_to_bytes(cx.local.verif_buf).unwrap();
cx.shared.tm_rb.lock(|prod| {
prod.sizes.try_push(tm.len_written()).unwrap();
prod.buf.push_slice(&cx.local.verif_buf[0..written_size]);
});
};
let request_id = cx.local.verif_reporter.read_request_id(&pus_tc);
let tm = cx
.local
.verif_reporter
.acceptance_success(cx.local.src_data_buf, &request_id, u14::ZERO, 0, &[])
.expect("acceptance success failed");
write_and_send(&tm);
let tm = cx
.local
.verif_reporter
.start_success(cx.local.src_data_buf, &request_id, u14::ZERO, 0, &[])
.expect("acceptance success failed");
write_and_send(&tm);
if pus_tc.service_type_id() == PusServiceId::Action as u8 {
let mut corrupt_image = |base_addr: u32| {
let mut buf = [0u8; 4];
cx.local.nvm.read(base_addr as usize + 32, &mut buf);
buf[0] += 1;
cx.local.nvm.write(base_addr as usize + 32, &buf);
let tm = cx
.local
.verif_reporter
.completion_success(cx.local.src_data_buf, &request_id, u14::ZERO, 0, &[])
.expect("completion success failed");
write_and_send(&tm);
};
if pus_tc.message_subtype_id() == ActionId::CorruptImageA as u8 {
defmt::info!("corrupting App Image A");
corrupt_image(APP_A_START_ADDR);
}
if pus_tc.message_subtype_id() == ActionId::CorruptImageB as u8 {
defmt::info!("corrupting App Image B");
corrupt_image(APP_B_START_ADDR);
}
if pus_tc.message_subtype_id() == ActionId::SetBootSlot as u8 {
if pus_tc.app_data().is_empty() {
defmt::warn!("App data for preferred image command too short");
}
let app_sel_result = AppSel::try_from(pus_tc.app_data()[0]);
if app_sel_result.is_err() {
defmt::warn!("Invalid app selection value: {}", pus_tc.app_data()[0]);
}
defmt::info!(
"received boot selection command with app select: {:?}",
app_sel_result.unwrap()
);
cx.local
.nvm
.write(PREFERRED_SLOT_OFFSET as usize, &[pus_tc.app_data()[0]]);
let tm = cx
.local
.verif_reporter
.completion_success(cx.local.src_data_buf, &request_id, u14::ZERO, 0, &[])
.expect("completion success failed");
write_and_send(&tm);
}
}
if pus_tc.service_type_id() == PusServiceId::Test as u8 && pus_tc.message_subtype_id() == 1
{
defmt::info!("received ping TC");
let tm = cx
.local
.verif_reporter
.completion_success(cx.local.src_data_buf, &request_id, u14::ZERO, 0, &[])
.expect("completion success failed");
write_and_send(&tm);
} else if pus_tc.service_type_id() == PusServiceId::MemoryManagement as u8 {
let tm = cx
.local
.verif_reporter
.step_success(
cx.local.src_data_buf,
&request_id,
u14::ZERO,
0,
&[],
EcssEnumU8::new(0),
)
.expect("step success failed");
write_and_send(&tm);
// Raw memory write TC
if pus_tc.message_subtype_id() == 2 {
let app_data = pus_tc.app_data();
if app_data.len() < 10 {
defmt::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 {
defmt::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() {
defmt::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];
defmt::info!("writing {} bytes at offset {} to NVM", data_len, offset);
cx.local.nvm.write(offset as usize, data);
let tm = if !cx.local.nvm.verify(offset as usize, data) {
defmt::warn!("verification of data written to NVM failed");
cx.local
.verif_reporter
.completion_failure(
cx.local.src_data_buf,
&request_id,
u14::ZERO,
0,
FailParams::new(&[], &EcssEnumU8::new(0), &[]),
)
.expect("completion success failed")
} else {
cx.local
.verif_reporter
.completion_success(cx.local.src_data_buf, &request_id, u14::ZERO, 0, &[])
.expect("completion success failed")
};
write_and_send(&tm);
defmt::info!("NVM operation done");
}
}
pub fn corrupt_image(base_addr: u32, nvm: &mut M95M01) {
let mut buf = [0u8; 4];
nvm.read(base_addr as usize + 32, &mut buf);
buf[0] += 1;
nvm.write(base_addr as usize + 32, &buf);
}
#[task(
priority = 1,
local=[
read_buf: [u8;MAX_TM_SIZE] = [0; MAX_TM_SIZE],
encoded_buf: [u8;MAX_TM_FRAME_SIZE] = [0; MAX_TM_FRAME_SIZE],
uart_tx,
tm_rx
],
shared=[tm_rb]
)]
async fn pus_tm_tx_handler(mut cx: pus_tm_tx_handler::Context) {
async fn tm_tx_handler(cx: tm_tx_handler::Context) {
let mut buf: [u8; 256] = [0; 256];
loop {
let mut occupied_len = cx.shared.tm_rb.lock(|rb| rb.sizes.occupied_len());
while occupied_len > 0 {
let next_size = cx.shared.tm_rb.lock(|rb| {
let next_size = rb.sizes.try_pop().unwrap();
rb.buf.pop_slice(&mut cx.local.read_buf[0..next_size]);
next_size
});
cx.local.encoded_buf[0] = 0;
let send_size = cobs::encode(
&cx.local.read_buf[0..next_size],
&mut cx.local.encoded_buf[1..],
);
cx.local.encoded_buf[send_size + 1] = 0;
cx.local
.uart_tx
.write_all(&cx.local.encoded_buf[0..send_size + 2])
.unwrap();
occupied_len -= 1;
Mono::delay(2.millis()).await;
let read_len = cx.local.tm_rx.read(&mut buf).await;
if let Err(e) = cx.local.uart_tx.write_all(&buf[0..read_len]).await {
defmt::warn!("UART TX overrun error: {}", e);
}
Mono::delay(50.millis()).await;
}
}
}
+4
View File
@@ -0,0 +1,4 @@
[toolchain]
channel = "stable"
components = ["clippy", "llvm-tools"]
target = "thumbv6m-none-eabi"
+35 -18
View File
@@ -47,6 +47,9 @@ pub use tx_async::*;
pub mod rx_async;
pub use rx_async::*;
/// FIFO depth of the UART for both the RX and TX FIFO.
pub const FIFO_DEPTH: usize = 16;
//==================================================================================================
// Type-Level support
//==================================================================================================
@@ -731,7 +734,7 @@ impl Uart {
Parity::Odd => (true, false),
Parity::Even => (true, true),
};
reg_block.write_ctrl(
reg_block.write_control(
Control::builder()
.with_baud8(config.clock_config.baud_mode == BaudMode::_8)
.with_auto_rts(false)
@@ -813,14 +816,14 @@ impl Uart {
}
pub fn listen(&mut self, event: Event) {
self.tx.regs.modify_irq_enabled(|mut value| {
self.tx.regs.modify_interrupt_enable(|mut value| {
match event {
Event::RxError => value.set_rx_status(true),
Event::RxFifoHalfFull => value.set_rx(true),
Event::RxTimeout => value.set_rx_timeout(true),
Event::TxEmpty => value.set_tx_empty(true),
Event::TxError => value.set_tx_status(true),
Event::TxFifoHalfFull => value.set_tx(true),
Event::TxFifoHalfFull => value.set_tx_below_trigger(true),
Event::TxCts => value.set_tx_cts(true),
}
value
@@ -828,14 +831,14 @@ impl Uart {
}
pub fn unlisten(&mut self, event: Event) {
self.tx.regs.modify_irq_enabled(|mut value| {
self.tx.regs.modify_interrupt_enable(|mut value| {
match event {
Event::RxError => value.set_rx_status(false),
Event::RxFifoHalfFull => value.set_rx(false),
Event::RxTimeout => value.set_rx_timeout(false),
Event::TxEmpty => value.set_tx_empty(false),
Event::TxError => value.set_tx_status(false),
Event::TxFifoHalfFull => value.set_tx(false),
Event::TxFifoHalfFull => value.set_tx_below_trigger(false),
Event::TxCts => value.set_tx_cts(false),
}
value
@@ -904,7 +907,7 @@ pub fn disable_rx(uart: &mut MmioUart<'static>) {
#[inline(always)]
pub fn enable_rx_interrupts(uart: &mut MmioUart<'static>, timeout: bool) {
uart.modify_irq_enabled(|mut value| {
uart.modify_interrupt_enable(|mut value| {
value.set_rx_status(true);
value.set_rx(true);
if timeout {
@@ -916,7 +919,7 @@ pub fn enable_rx_interrupts(uart: &mut MmioUart<'static>, timeout: bool) {
#[inline(always)]
pub fn disable_rx_interrupts(uart: &mut MmioUart<'static>) {
uart.modify_irq_enabled(|mut value| {
uart.modify_interrupt_enable(|mut value| {
value.set_rx_status(false);
value.set_rx(false);
value.set_rx_timeout(false);
@@ -1035,6 +1038,13 @@ impl Rx {
self.regs.read_data().raw_value()
}
#[inline]
pub fn into_rx_with_interrupt(self) -> RxWithInterrupt {
RxWithInterrupt::new(self)
}
#[deprecated(since = "0.3.0", note = "Use into_rx_with_interrupt instead")]
#[inline]
pub fn into_rx_with_irq(self) -> RxWithInterrupt {
RxWithInterrupt::new(self)
}
@@ -1101,9 +1111,9 @@ pub fn disable_tx(uart: &mut MmioUart<'static>) {
}
#[inline(always)]
pub fn enable_tx_interrupts(uart: &mut MmioUart<'static>) {
uart.modify_irq_enabled(|mut value| {
value.set_tx(true);
pub fn enable_tx_interrupts(tx_below_trigger: bool, uart: &mut MmioUart<'static>) {
uart.modify_interrupt_enable(|mut value| {
value.set_tx_below_trigger(tx_below_trigger);
value.set_tx_empty(true);
value.set_tx_status(true);
value
@@ -1112,8 +1122,8 @@ pub fn enable_tx_interrupts(uart: &mut MmioUart<'static>) {
#[inline(always)]
pub fn disable_tx_interrupts(uart: &mut MmioUart<'static>) {
uart.modify_irq_enabled(|mut value| {
value.set_tx(false);
uart.modify_interrupt_enable(|mut value| {
value.set_tx_below_trigger(false);
value.set_tx_empty(false);
value.set_tx_status(false);
value
@@ -1176,18 +1186,24 @@ impl Tx {
/// Enables the IRQ_TX, IRQ_TX_STATUS and IRQ_TX_EMPTY interrupts.
///
/// - The IRQ_TX interrupt is generated when the TX FIFO is at least half empty.
/// - The IRQ_TX interrupt is generated when the TX FIFO is at least half empty and the
/// `tx_below_trigger` parameter is set to `true`. This should be set to true if the total
/// amount of data to be transmitted is larger than the FIFO size.
/// - The IRQ_TX_STATUS interrupt is generated when write data is lost due to a FIFO overflow
/// - The IRQ_TX_EMPTY interrupt is generated when the TX FIFO is empty and the TXBUSY signal
/// is 0
#[inline]
pub fn enable_interrupts(&mut self, #[cfg(feature = "vor4x")] enable_in_nvic: bool) {
pub fn enable_interrupts(
&mut self,
tx_below_trigger: bool,
#[cfg(feature = "vor4x")] enable_in_nvic: bool,
) {
#[cfg(feature = "vor4x")]
if enable_in_nvic {
unsafe { enable_nvic_interrupt(self.id.interrupt_id_tx()) };
}
// Safety: We own the UART structure
enable_tx_interrupts(&mut self.regs);
enable_tx_interrupts(tx_below_trigger, &mut self.regs);
}
/// Disables the IRQ_TX, IRQ_TX_STATUS and IRQ_TX_EMPTY interrupts.
@@ -1297,6 +1313,7 @@ impl embedded_io::Write for Tx {
pub struct RxWithInterrupt(Rx);
impl RxWithInterrupt {
#[inline]
pub fn new(rx: Rx) -> Self {
Self(rx)
}
@@ -1378,8 +1395,8 @@ impl RxWithInterrupt {
pub fn on_interrupt(&mut self, buf: &mut [u8; 16]) -> InterruptResult {
let mut result = InterruptResult::default();
let irq_status = self.0.regs.read_irq_status();
let irq_enabled = self.0.regs.read_irq_enabled();
let irq_status = self.0.regs.read_interrupt_status();
let irq_enabled = self.0.regs.read_interrupt_enable();
let rx_enabled = irq_enabled.rx();
// Half-Full interrupt. We have a guaranteed amount of data we can read.
@@ -1443,7 +1460,7 @@ impl RxWithInterrupt {
}
let mut result = InterruptResultMaxSizeOrTimeout::default();
let irq_status = self.0.regs.read_irq_status();
let irq_status = self.0.regs.read_interrupt_status();
let rx_enabled = self.0.regs.read_enable().rx();
// Half-Full interrupt. We have a guaranteed amount of data we can read.
+6 -6
View File
@@ -188,7 +188,7 @@ pub struct InterruptControl {
/// Generates an interrupt when the TX FIFO is at least half-empty (FIFO count < trigger level)
#[bit(4, rw)]
tx: bool,
tx_below_trigger: bool,
/// Generates an interrupt on TX FIFO overflow.
#[bit(5, rw)]
tx_status: bool,
@@ -213,7 +213,7 @@ pub struct InterruptStatus {
/// Generates an interrupt when the TX FIFO is at least half-empty (FIFO count < trigger level)
#[bit(4, r)]
tx: bool,
tx_below_trigger: bool,
/// Generates an interrupt on TX FIFO overflow.
#[bit(5, r)]
tx_status: bool,
@@ -262,7 +262,7 @@ pub struct State {
pub struct Uart {
data: Data,
enable: Enable,
ctrl: Control,
control: Control,
clkscale: ClockScale,
#[mmio(PureRead)]
rx_status: RxStatus,
@@ -274,11 +274,11 @@ pub struct Uart {
txbreak: u32,
addr9: u32,
addr9mask: u32,
irq_enabled: InterruptControl,
interrupt_enable: InterruptControl,
#[mmio(PureRead)]
irq_raw: InterruptStatus,
interrupt_raw: InterruptStatus,
#[mmio(PureRead)]
irq_status: InterruptStatus,
interrupt_status: InterruptStatus,
#[mmio(Write)]
irq_clr: InterruptClear,
rx_fifo_trigger: FifoTrigger,
+4 -4
View File
@@ -134,8 +134,8 @@ pub fn on_interrupt_rx_async_heapless_queue_overwriting(
shared_consumer: &Mutex<RefCell<Option<heapless::spsc::Consumer<'static, u8>>>>,
) -> Result<(), AsyncUartErrors> {
let uart_regs = unsafe { bank.steal_regs() };
let irq_status = uart_regs.read_irq_status();
let irq_enabled = uart_regs.read_irq_enabled();
let irq_status = uart_regs.read_interrupt_status();
let irq_enabled = uart_regs.read_interrupt_enable();
let rx_enabled = irq_enabled.rx();
let mut read_some_data = false;
let mut queue_overflow = false;
@@ -202,8 +202,8 @@ pub fn on_interrupt_rx_async_heapless_queue(
prod: &mut heapless::spsc::Producer<'_, u8>,
) -> Result<(), AsyncUartErrors> {
let uart_regs = unsafe { bank.steal_regs() };
let irq_status = uart_regs.read_irq_status();
let irq_enabled = uart_regs.read_irq_enabled();
let irq_status = uart_regs.read_interrupt_status();
let irq_enabled = uart_regs.read_interrupt_enable();
let rx_enabled = irq_enabled.rx();
let mut read_some_data = false;
let mut queue_overflow = false;
+15 -12
View File
@@ -38,13 +38,14 @@ fn tx_is_drained(tx: &Tx) -> bool {
pub fn on_interrupt_tx(bank: Bank) {
let mut uart = unsafe { bank.steal_regs() };
let idx = bank as usize;
let irq_enabled = uart.read_irq_enabled();
let irq_enabled = uart.read_interrupt_enable();
// IRQ is not related to TX.
if !irq_enabled.tx() && !irq_enabled.tx_empty() {
if !irq_enabled.tx_below_trigger() && !irq_enabled.tx_empty() {
return;
}
let tx_status = uart.read_tx_status();
let interrupt_status = uart.read_interrupt_status();
let unexpected_overrun = tx_status.wr_lost();
let mut context = critical_section::with(|cs| {
let context_ref = TX_CONTEXTS[idx].borrow(cs);
@@ -54,17 +55,14 @@ pub fn on_interrupt_tx(bank: Bank) {
// Safety: We documented that the user provided slice must outlive the future, so we convert
// the raw pointer back to the slice here.
let slice = unsafe { context.slice.get().unwrap() };
if context.progress >= slice.len() && !tx_status.tx_busy() {
uart.modify_irq_enabled(|mut value| {
value.set_tx(false);
value.set_tx_empty(false);
value.set_tx_status(false);
value
});
uart.modify_enable(|mut value| {
value.set_tx(false);
if context.progress >= slice.len() && interrupt_status.tx_empty() {
uart.modify_interrupt_enable(|value| {
value
.with_tx_below_trigger(false)
.with_tx_empty(false)
.with_tx_status(false)
});
uart.modify_enable(|value| value.with_tx(false));
// Write back updated context structure.
critical_section::with(|cs| {
let context_ref = TX_CONTEXTS[idx].borrow(cs);
@@ -84,6 +82,10 @@ pub fn on_interrupt_tx(bank: Bank) {
uart.write_data(Data::new_with_raw_value(slice[context.progress] as u32));
context.progress += 1;
}
// Now we only require the TX empty interrupt.
if context.progress == slice.len() {
uart.modify_interrupt_enable(|value| value.with_tx_below_trigger(false));
}
// Write back updated context structure.
critical_section::with(|cs| {
@@ -125,7 +127,7 @@ impl TxFuture {
tx.disable();
tx.clear_fifo();
let init_fill_count = core::cmp::min(data.len(), 16);
let init_fill_count = core::cmp::min(data.len(), FIFO_DEPTH);
// We fill the FIFO.
for data in data.iter().take(init_fill_count) {
tx.regs.write_data(Data::new_with_raw_value(*data as u32));
@@ -139,6 +141,7 @@ impl TxFuture {
// Ensure those are enabled inside a critical section at the same time. Can lead to
// weird glitches otherwise.
tx.enable_interrupts(
data.len() > init_fill_count,
#[cfg(feature = "vor4x")]
true,
);