diff --git a/flashloader/image-loader.py b/flashloader/image-loader.py index f5a9059..d12b658 100755 --- a/flashloader/image-loader.py +++ b/flashloader/image-loader.py @@ -43,6 +43,7 @@ ACTION_SERVICE = 8 RAW_MEMORY_WRITE_SUBSERVICE = 2 BOOT_NVM_MEMORY_ID = 1 +PING_PAYLOAD_SIZE = 0 class ActionId(enum.IntEnum): @@ -102,6 +103,29 @@ def main() -> int: com_if = SerialCobsComIF(serial_cfg) com_if.open() file_path = None + if args.ping: + _LOGGER.info("Sending ping command") + ping_tc = PusTc( + apid=0x00, + service=PusService.S17_TEST, + subservice=1, + seq_count=SEQ_PROVIDER.get_and_increment(), + app_data=bytes(PING_PAYLOAD_SIZE), + ) + verificator.add_tc(ping_tc) + com_if.send(ping_tc.pack()) + + data_available = com_if.data_available(0.4) + if not data_available: + _LOGGER.warning("no ping reply received") + for reply in com_if.receive(): + result = verificator.add_tm( + Service1Tm.from_tm(PusTm.unpack(reply, 0), UnpackParams(0)) + ) + if result is not None and result.completed: + _LOGGER.info("received ping completion reply") + if not args.target: + return 0 if args.target: if not args.corrupt: if not args.path: @@ -111,15 +135,6 @@ def main() -> int: if not file_path.exists(): _LOGGER.error("File does not exist") return -1 - if args.ping: - _LOGGER.info("Sending ping command") - ping_tc = PusTc( - apid=0x00, - service=PusService.S17_TEST, - subservice=1, - seq_count=SEQ_PROVIDER.get_and_increment(), - ) - com_if.send(ping_tc.pack()) if args.corrupt: if not args.target: _LOGGER.error("target for corruption command required") @@ -252,7 +267,7 @@ def main() -> int: ): done = True # Still keep a small delay - time.sleep(0.01) + # time.sleep(0.05) verificator.remove_completed_entries() if done: break diff --git a/flashloader/src/main.rs b/flashloader/src/main.rs index a1e3f3e..c9bd62f 100644 --- a/flashloader/src/main.rs +++ b/flashloader/src/main.rs @@ -18,13 +18,11 @@ #![no_main] #![no_std] -use embedded_hal_nb::serial::Read; use once_cell::sync::OnceCell; use panic_rtt_target as _; use va416xx_hal::{clock::Clocks, edac, pac, time::Hertz, wdt::Wdt}; const EXTCLK_FREQ: u32 = 40_000_000; -const COBS_FRAME_SEPARATOR: u8 = 0x0; const MAX_TC_SIZE: usize = 1024; const MAX_TC_FRAME_SIZE: usize = cobs::max_encoding_length(MAX_TC_SIZE); @@ -33,10 +31,8 @@ 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 SERIAL_RX_WIRETAPPING: bool = false; -const COBS_RX_DEBUGGING: bool = false; - const BOOT_NVM_MEMORY_ID: u8 = 1; +const RX_DEBUGGING: bool = false; pub enum ActionId { CorruptImageA = 128, @@ -62,13 +58,24 @@ use ringbuf::{ CachingCons, StaticProd, StaticRb, }; -const BUF_RB_SIZE_TX: usize = 1024; -const SIZES_RB_SIZE_TX: usize = 16; +// Larger buffer for TC to be able to hold the possibly large memory write packets. +const BUF_RB_SIZE_TC: usize = 2048; +const SIZES_RB_SIZE_TC: usize = 16; -static mut BUF_RB_TX: Lazy> = - Lazy::new(StaticRb::::default); -static mut SIZES_RB_TX: Lazy> = - Lazy::new(StaticRb::::default); +const BUF_RB_SIZE_TM: usize = 512; +const SIZES_RB_SIZE_TM: usize = 16; + +// Ring buffers to handling variable sized telemetry +static mut BUF_RB_TM: Lazy> = + Lazy::new(StaticRb::::default); +static mut SIZES_RB_TM: Lazy> = + Lazy::new(StaticRb::::default); + +// Ring buffers to handling variable sized telecommands +static mut BUF_RB_TC: Lazy> = + Lazy::new(StaticRb::::default); +static mut SIZES_RB_TC: Lazy> = + Lazy::new(StaticRb::::default); pub struct DataProducer { pub buf_prod: StaticProd<'static, u8, BUF_SIZE>, @@ -91,15 +98,10 @@ pub const APP_B_END_ADDR: u32 = 0x40000; mod app { use super::*; use cortex_m::asm; - use embedded_hal_nb::nb; use embedded_io::Write; use panic_rtt_target as _; use rtic::Mutex; use rtic_monotonics::systick::prelude::*; - use rtic_sync::{ - channel::{Receiver, Sender}, - make_channel, - }; use rtt_target::rprintln; use satrs::pus::verification::VerificationReportCreator; use spacepackets::ecss::PusServiceId; @@ -127,26 +129,24 @@ mod app { #[local] struct Local { - uart_rx: uart::Rx, + uart_rx: uart::RxWithIrq, uart_tx: uart::Tx, - cobs_reader_state: CobsReaderStates, - tc_tx: TcTx, - tc_rx: TcRx, rom_spi: Option, - tx_cons: DataConsumer, + // We handle all TM in one task. + tm_cons: DataConsumer, + // We consume all TC in one task. + tc_cons: DataConsumer, + // We produce all TC in one task. + tc_prod: DataProducer, verif_reporter: VerificationReportCreator, } #[shared] struct Shared { - decode_buffer_busy: bool, - decode_buf: [u8; MAX_TC_SIZE], - tx_prod: DataProducer, + // Having this shared allows multiple tasks to generate telemetry. + tm_prod: DataProducer, } - pub type TcTx = Sender<'static, usize, 2>; - pub type TcRx = Receiver<'static, usize, 2>; - rtic_monotonics::systick_monotonic!(Mono, 10_000); #[init] @@ -176,38 +176,45 @@ mod app { &mut cx.device.sysconfig, &clocks, ); - let (tx, rx) = uart0.split(); - let (tc_tx, tc_rx) = make_channel!(usize, 2); + let (tx, mut rx, _) = uart0.split_with_irq(); let verif_reporter = VerificationReportCreator::new(0).unwrap(); - let (buf_prod, buf_cons) = unsafe { BUF_RB_TX.split_ref() }; - let (sizes_prod, sizes_cons) = unsafe { SIZES_RB_TX.split_ref() }; + let (buf_prod_tm, buf_cons_tm) = unsafe { BUF_RB_TM.split_ref() }; + let (sizes_prod_tm, sizes_cons_tm) = unsafe { SIZES_RB_TM.split_ref() }; + + let (buf_prod_tc, buf_cons_tc) = unsafe { BUF_RB_TC.split_ref() }; + let (sizes_prod_tc, sizes_cons_tc) = unsafe { SIZES_RB_TC.split_ref() }; Mono::start(cx.core.SYST, clocks.sysclk().raw()); CLOCKS.set(clocks).unwrap(); + + rx.read_fixed_len_using_irq(MAX_TC_FRAME_SIZE, true) + .expect("initiating UART RX failed"); pus_tc_handler::spawn().unwrap(); - uart_reader_task::spawn().unwrap(); pus_tm_tx_handler::spawn().unwrap(); ( Shared { - decode_buffer_busy: false, - decode_buf: [0; MAX_TC_SIZE], - tx_prod: DataProducer { - buf_prod, - sizes_prod, + tm_prod: DataProducer { + buf_prod: buf_prod_tm, + sizes_prod: sizes_prod_tm, }, }, Local { uart_rx: rx, uart_tx: tx, - cobs_reader_state: CobsReaderStates::default(), - tc_tx, - tc_rx, rom_spi: Some(cx.device.spi3), - tx_cons: DataConsumer { - buf_cons, - sizes_cons, + tm_cons: DataConsumer { + buf_cons: buf_cons_tm, + sizes_cons: sizes_cons_tm, + }, + tc_cons: DataConsumer { + buf_cons: buf_cons_tc, + sizes_cons: sizes_cons_tc, + }, + tc_prod: DataProducer { + buf_prod: buf_prod_tc, + sizes_prod: sizes_prod_tc, }, verif_reporter, }, @@ -223,120 +230,62 @@ mod app { } #[task( - priority = 4, - local=[ - read_buf: [u8;MAX_TC_FRAME_SIZE] = [0; MAX_TC_FRAME_SIZE], + binds = UART0_RX, + local = [ + cnt: u32 = 0, + rx_buf: [u8; MAX_TC_FRAME_SIZE] = [0; MAX_TC_FRAME_SIZE], uart_rx, - cobs_reader_state, - tc_tx + tc_prod ], - shared=[decode_buffer_busy, decode_buf] )] - async fn uart_reader_task(mut cx: uart_reader_task::Context) { - let mut current_idx = 0; - loop { - match cx.local.uart_rx.read() { - Ok(byte) => { - if SERIAL_RX_WIRETAPPING { - log::debug!("RX Byte: 0x{:x?}", byte); - } - handle_single_rx_byte(&mut cx, byte, &mut current_idx) + fn uart_rx_irq(cx: uart_rx_irq::Context) { + match cx.local.uart_rx.irq_handler(cx.local.rx_buf) { + Ok(result) => { + if RX_DEBUGGING { + log::debug!("RX Info: {:?}", cx.local.uart_rx.irq_info()); + log::debug!("RX Result: {:?}", result); } - Err(e) => { - match e { - nb::Error::Other(e) => { - log::warn!("UART error: {:?}", e); - match e { - uart::Error::Overrun => { - cx.local.uart_rx.clear_fifo(); - } - uart::Error::FramingError => (), - uart::Error::ParityError => (), - uart::Error::BreakCondition => (), - uart::Error::TransferPending => (), - uart::Error::BufferTooShort => (), + 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() { + log::warn!("COBS decoding failed"); + } else { + let decoded_size = decoded_size.unwrap(); + if cx.local.tc_prod.sizes_prod.vacant_len() >= 1 + && cx.local.tc_prod.buf_prod.vacant_len() >= decoded_size + { + // Should never fail, we checked there is enough space. + cx.local.tc_prod.sizes_prod.try_push(decoded_size).unwrap(); + cx.local + .tc_prod + .buf_prod + .push_slice(&cx.local.rx_buf[1..1 + decoded_size]); + } else { + log::warn!("COBS TC queue full"); } } - nb::Error::WouldBlock => { - // Delay for a short period before polling again. - Mono::delay(400.micros()).await; - } + } else { + log::warn!("COBS frame with invalid format, start and end bytes are not 0"); } - } - } - } - } - fn handle_single_rx_byte( - cx: &mut uart_reader_task::Context, - byte: u8, - current_idx: &mut usize, - ) { - match cx.local.cobs_reader_state { - CobsReaderStates::WaitingForStart => { - if byte == COBS_FRAME_SEPARATOR { - if COBS_RX_DEBUGGING { - log::debug!("COBS start marker detected"); - } - *cx.local.cobs_reader_state = CobsReaderStates::WatingForEnd; - *current_idx = 0; + // Initiate next transfer. + cx.local + .uart_rx + .read_fixed_len_using_irq(MAX_TC_FRAME_SIZE, true) + .expect("read operation failed"); + } + if result.error() { + log::warn!("UART error: {:?}", result.error()); } } - CobsReaderStates::WatingForEnd => { - if byte == COBS_FRAME_SEPARATOR { - if COBS_RX_DEBUGGING { - log::debug!("COBS end marker detected"); - } - 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 COBS_RX_DEBUGGING { - log::debug!( - "COBS decoded packet with length {}", - packet_len - ); - } - if cx.local.tc_tx.try_send(packet_len).is_err() { - sending_failed = true; - } - *busy = true; - } - Err(_) => { - decoding_error = 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"); - } - *cx.local.cobs_reader_state = CobsReaderStates::WaitingForStart; - } 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) => { + log::warn!("UART error: {:?}", e); } } } @@ -344,149 +293,167 @@ mod app { #[task( priority = 2, local=[ - read_buf: [u8;MAX_TC_FRAME_SIZE] = [0; MAX_TC_FRAME_SIZE], + tc_buf: [u8; MAX_TC_SIZE] = [0; MAX_TC_SIZE], src_data_buf: [u8; 16] = [0; 16], verif_buf: [u8; 32] = [0; 32], - tc_rx, + tc_cons, rom_spi, verif_reporter ], - shared=[decode_buffer_busy, decode_buf, tx_prod] + shared=[tm_prod] )] async fn pus_tc_handler(mut cx: pus_tc_handler::Context) { loop { - let packet_len = cx.local.tc_rx.recv().await.expect("all senders down"); + // Try to read a TC from the ring buffer. + let packet_len = cx.local.tc_cons.sizes_cons.try_pop(); + if packet_len.is_none() { + // Small delay, TCs might arrive very quickly. + Mono::delay(20.millis()).await; + continue; + } + let packet_len = packet_len.unwrap(); log::info!(target: "TC Handler", "received packet with length {}", packet_len); - // 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, _)) => { - let mut write_and_send = |tm: &PusTmCreator| { - let written_size = tm.write_to_bytes(cx.local.verif_buf).unwrap(); - cx.shared.tx_prod.lock(|prod| { - prod.sizes_prod.try_push(tm.len_written()).unwrap(); - prod.buf_prod - .push_slice(&cx.local.verif_buf[0..written_size]); - }); - }; - let token = cx.local.verif_reporter.add_tc(&pus_tc); - let (tm, accepted_token) = cx - .local - .verif_reporter - .acceptance_success(cx.local.src_data_buf, token, 0, 0, &[]) - .expect("acceptance success failed"); - write_and_send(&tm); + assert_eq!( + cx.local + .tc_cons + .buf_cons + .pop_slice(&mut cx.local.tc_buf[0..packet_len]), + packet_len + ); + // Read a telecommand, now handle it. + handle_valid_pus_tc(&mut cx); + } + } - let (tm, started_token) = cx - .local - .verif_reporter - .start_success(cx.local.src_data_buf, accepted_token, 0, 0, &[]) - .expect("acceptance success failed"); - write_and_send(&tm); + fn handle_valid_pus_tc(cx: &mut pus_tc_handler::Context) { + let pus_tc = PusTcReader::new(cx.local.tc_buf); + if pus_tc.is_err() { + log::warn!("PUS TC error: {}", pus_tc.unwrap_err()); + 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_prod.lock(|prod| { + prod.sizes_prod.try_push(tm.len_written()).unwrap(); + prod.buf_prod + .push_slice(&cx.local.verif_buf[0..written_size]); + }); + }; + let token = cx.local.verif_reporter.add_tc(&pus_tc); + let (tm, accepted_token) = cx + .local + .verif_reporter + .acceptance_success(cx.local.src_data_buf, token, 0, 0, &[]) + .expect("acceptance success failed"); + write_and_send(&tm); - if pus_tc.service() == PusServiceId::Action as u8 { - let mut corrupt_image = |base_addr: u32| { - // 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(), - ); - let mut buf = [0u8; 4]; - nvm.read_data(base_addr + 32, &mut buf); - buf[0] += 1; - nvm.write_data(base_addr + 32, &buf); - *cx.local.rom_spi = Some(nvm.release(&mut sys_cfg)); - let tm = cx - .local - .verif_reporter - .completion_success(cx.local.src_data_buf, started_token, 0, 0, &[]) - .expect("completion success failed"); - write_and_send(&tm); - }; - if pus_tc.subservice() == ActionId::CorruptImageA as u8 { - rprintln!("corrupting App Image A"); - corrupt_image(APP_A_START_ADDR); - } - if pus_tc.subservice() == ActionId::CorruptImageB as u8 { - rprintln!("corrupting App Image B"); - corrupt_image(APP_B_START_ADDR); - } - } - if pus_tc.service() == PusServiceId::Test as u8 && pus_tc.subservice() == 1 { - log::info!(target: "TC Handler", "received ping TC"); - } else if pus_tc.service() == PusServiceId::MemoryManagement as u8 { - let tm = cx - .local - .verif_reporter - .step_success( - cx.local.src_data_buf, - &started_token, - 0, - 0, - &[], - EcssEnumU8::new(0), - ) - .expect("step success failed"); - write_and_send(&tm); - // Raw memory write TC - if pus_tc.subservice() == 2 { - let app_data = pus_tc.app_data(); - if app_data.len() < 10 { - log::warn!( - target: "TC Handler", - "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!(target: "TC Handler", "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!( - target: "TC Handler", - "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)); - let tm = cx - .local - .verif_reporter - .completion_success(cx.local.src_data_buf, started_token, 0, 0, &[]) - .expect("completion success failed"); - write_and_send(&tm); - log::info!("NVM operation done"); - } - } + let (tm, started_token) = cx + .local + .verif_reporter + .start_success(cx.local.src_data_buf, accepted_token, 0, 0, &[]) + .expect("acceptance success failed"); + write_and_send(&tm); + + if pus_tc.service() == PusServiceId::Action as u8 { + let mut corrupt_image = |base_addr: u32| { + // 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(), + ); + let mut buf = [0u8; 4]; + nvm.read_data(base_addr + 32, &mut buf); + buf[0] += 1; + nvm.write_data(base_addr + 32, &buf); + *cx.local.rom_spi = Some(nvm.release(&mut sys_cfg)); + let tm = cx + .local + .verif_reporter + .completion_success(cx.local.src_data_buf, started_token, 0, 0, &[]) + .expect("completion success failed"); + write_and_send(&tm); + }; + if pus_tc.subservice() == ActionId::CorruptImageA as u8 { + rprintln!("corrupting App Image A"); + corrupt_image(APP_A_START_ADDR); + } + if pus_tc.subservice() == ActionId::CorruptImageB as u8 { + rprintln!("corrupting App Image B"); + corrupt_image(APP_B_START_ADDR); + } + } + if pus_tc.service() == PusServiceId::Test as u8 && pus_tc.subservice() == 1 { + log::info!(target: "TC Handler", "received ping TC"); + let tm = cx + .local + .verif_reporter + .completion_success(cx.local.src_data_buf, started_token, 0, 0, &[]) + .expect("completion success failed"); + write_and_send(&tm); + } else if pus_tc.service() == PusServiceId::MemoryManagement as u8 { + let tm = cx + .local + .verif_reporter + .step_success( + cx.local.src_data_buf, + &started_token, + 0, + 0, + &[], + EcssEnumU8::new(0), + ) + .expect("step success failed"); + write_and_send(&tm); + // Raw memory write TC + if pus_tc.subservice() == 2 { + let app_data = pus_tc.app_data(); + if app_data.len() < 10 { + log::warn!( + target: "TC Handler", + "app data for raw memory write is too short: {}", + app_data.len() + ); } - Err(e) => { - log::warn!("PUS TC error: {}", e); + let memory_id = app_data[0]; + if memory_id != BOOT_NVM_MEMORY_ID { + log::warn!(target: "TC Handler", "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!( + target: "TC Handler", + "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)); + let tm = cx + .local + .verif_reporter + .completion_success(cx.local.src_data_buf, started_token, 0, 0, &[]) + .expect("completion success failed"); + write_and_send(&tm); + log::info!("NVM operation done"); } } } @@ -497,16 +464,16 @@ mod app { read_buf: [u8;MAX_TM_SIZE] = [0; MAX_TM_SIZE], encoded_buf: [u8;MAX_TM_FRAME_SIZE] = [0; MAX_TM_FRAME_SIZE], uart_tx, - tx_cons, + tm_cons ], shared=[] )] async fn pus_tm_tx_handler(cx: pus_tm_tx_handler::Context) { loop { - while cx.local.tx_cons.sizes_cons.occupied_len() > 0 { - let next_size = cx.local.tx_cons.sizes_cons.try_pop().unwrap(); + while cx.local.tm_cons.sizes_cons.occupied_len() > 0 { + let next_size = cx.local.tm_cons.sizes_cons.try_pop().unwrap(); cx.local - .tx_cons + .tm_cons .buf_cons .pop_slice(&mut cx.local.read_buf[0..next_size]); cx.local.encoded_buf[0] = 0; diff --git a/va416xx-hal/CHANGELOG.md b/va416xx-hal/CHANGELOG.md index 21c4f73..14790d4 100644 --- a/va416xx-hal/CHANGELOG.md +++ b/va416xx-hal/CHANGELOG.md @@ -17,12 +17,14 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - Added `va41620`, `va41630`, `va41628` and `va41629` device features. A device now has to be selected for HAL compilation to work properly +- Adaptions for the UART IRQ feature which are now only implemented for the RX part of the UART. ## Fixed - Small fixes and improvements for ADC drivers - Fixes for the SPI implementation where the clock divider values were not calculated correctly +- Fixes for UART IRQ handler implementation ## Added diff --git a/va416xx-hal/Cargo.toml b/va416xx-hal/Cargo.toml index 0f4a894..038b44e 100644 --- a/va416xx-hal/Cargo.toml +++ b/va416xx-hal/Cargo.toml @@ -20,7 +20,7 @@ embedded-io = "0.6" num_enum = { version = "0.7", default-features = false } typenum = "1" bitflags = "2" -bitfield = "0.15" +bitfield = "0.17" defmt = { version = "0.3", optional = true } fugit = "0.3" delegate = "0.12" diff --git a/va416xx-hal/src/uart.rs b/va416xx-hal/src/uart.rs index 5b3eed9..0dfaf9b 100644 --- a/va416xx-hal/src/uart.rs +++ b/va416xx-hal/src/uart.rs @@ -197,66 +197,36 @@ impl From for Config { // IRQ Definitions //================================================================================================== -struct IrqInfo { +#[derive(Debug)] +pub struct IrqInfo { rx_len: usize, rx_idx: usize, mode: IrqReceptionMode, } -pub enum IrqResultMask { - Complete = 0, - Overflow = 1, - FramingError = 2, - ParityError = 3, - Break = 4, - Timeout = 5, - Addr9 = 6, - /// Should not happen - Unknown = 7, -} - /// This struct is used to return the default IRQ handler result to the user #[derive(Debug, Default)] pub struct IrqResult { - raw_res: u32, + complete: bool, + timeout: bool, + pub errors: IrqUartError, pub bytes_read: usize, } impl IrqResult { - pub const fn new() -> Self { + pub fn new() -> Self { IrqResult { - raw_res: 0, + complete: false, + timeout: false, + errors: IrqUartError::default(), bytes_read: 0, } } } - impl IrqResult { - #[inline] - pub fn raw_result(&self) -> u32 { - self.raw_res - } - - #[inline] - pub(crate) fn clear_result(&mut self) { - self.raw_res = 0; - } - #[inline] - pub(crate) fn set_result(&mut self, flag: IrqResultMask) { - self.raw_res |= 1 << flag as u32; - } - - #[inline] - pub fn complete(&self) -> bool { - if ((self.raw_res >> IrqResultMask::Complete as u32) & 0x01) == 0x01 { - return true; - } - false - } - #[inline] pub fn error(&self) -> bool { - if self.overflow_error() || self.framing_error() || self.parity_error() { + if self.errors.overflow || self.errors.parity || self.errors.framing { return true; } false @@ -264,34 +234,27 @@ impl IrqResult { #[inline] pub fn overflow_error(&self) -> bool { - if ((self.raw_res >> IrqResultMask::Overflow as u32) & 0x01) == 0x01 { - return true; - } - false + self.errors.overflow } #[inline] pub fn framing_error(&self) -> bool { - if ((self.raw_res >> IrqResultMask::FramingError as u32) & 0x01) == 0x01 { - return true; - } - false + self.errors.framing } #[inline] pub fn parity_error(&self) -> bool { - if ((self.raw_res >> IrqResultMask::ParityError as u32) & 0x01) == 0x01 { - return true; - } - false + self.errors.parity } #[inline] pub fn timeout(&self) -> bool { - if ((self.raw_res >> IrqResultMask::Timeout as u32) & 0x01) == 0x01 { - return true; - } - false + self.timeout + } + + #[inline] + pub fn complete(&self) -> bool { + self.complete } } @@ -317,41 +280,27 @@ pub struct Uart { pins: Pins, } -/// UART using the IRQ capabilities of the peripheral. Can be created with the -/// [`Uart::into_uart_with_irq`] function. Currently, only the RX side for IRQ based reception -/// is implemented. -pub struct UartWithIrq { - base: UartWithIrqBase, - pins: Pins, -} +/// Serial receiver +pub struct Rx(Uart); -/// Type-erased UART using the IRQ capabilities of the peripheral. Can be created with the -/// [`UartWithIrq::downgrade`] function. Currently, only the RX side for IRQ based reception -/// is implemented. -pub struct UartWithIrqBase { - pub inner: UartBase, +// Serial receiver, using interrupts to offload reading to the hardware. +pub struct RxWithIrq { + inner: Rx, irq_info: IrqInfo, } -/// Serial receiver -pub struct Rx { - uart: Uart, -} - /// Serial transmitter -pub struct Tx { - uart: Uart, -} +pub struct Tx(Uart); impl Rx { fn new(uart: Uart) -> Self { - Self { uart } + Self(uart) } } impl Tx { fn new(uart: Uart) -> Self { - Self { uart } + Self(uart) } } @@ -602,20 +551,30 @@ impl, RxPinInst: RxPin, UartInstanc } /// If the IRQ capabilities of the peripheral are used, the UART needs to be converted - /// with this function - pub fn into_uart_with_irq(self) -> UartWithIrq { + /// with this function. Currently, IRQ abstractions are only implemented for the RX part + /// of the UART, so this function will release a TX and RX handle as well as the pin + /// instances. + pub fn split_with_irq( + self, + ) -> ( + Tx, + RxWithIrq, + (TxPinInst, RxPinInst), + ) { let (inner, pins) = self.downgrade_internal(); - UartWithIrq { - pins, - base: UartWithIrqBase { - inner, + let (tx, rx) = inner.split(); + ( + tx, + RxWithIrq { + inner: rx, irq_info: IrqInfo { rx_len: 0, rx_idx: 0, mode: IrqReceptionMode::Idle, }, }, - } + pins, + ) } delegate::delegate! { @@ -673,11 +632,26 @@ impl Rx { /// /// You must ensure that only registers related to the operation of the RX side are used. pub unsafe fn uart(&self) -> &Uart { - &self.uart + &self.0 } + #[inline] pub fn clear_fifo(&self) { - self.uart.fifo_clr().write(|w| w.rxfifo().set_bit()); + self.0.fifo_clr().write(|w| w.rxfifo().set_bit()); + } + + #[inline] + pub fn enable(&mut self) { + self.0.enable().modify(|_, w| w.rxenable().set_bit()); + } + + #[inline] + pub fn disable(&mut self) { + self.0.enable().modify(|_, w| w.rxenable().clear_bit()); + } + + pub fn release(self) -> Uart { + self.0 } } @@ -688,11 +662,22 @@ impl Tx { /// /// You must ensure that only registers related to the operation of the TX side are used. pub unsafe fn uart(&self) -> &Uart { - &self.uart + &self.0 } + #[inline] pub fn clear_fifo(&self) { - self.uart.fifo_clr().write(|w| w.txfifo().set_bit()); + self.0.fifo_clr().write(|w| w.txfifo().set_bit()); + } + + #[inline] + pub fn enable(&mut self) { + self.0.enable().modify(|_, w| w.txenable().set_bit()); + } + + #[inline] + pub fn disable(&mut self) { + self.0.enable().modify(|_, w| w.txenable().clear_bit()); } } @@ -701,6 +686,7 @@ pub struct IrqUartError { overflow: bool, framing: bool, parity: bool, + other: bool, } impl IrqUartError { @@ -715,7 +701,7 @@ pub enum IrqError { Uart(IrqUartError), } -impl UartWithIrqBase { +impl RxWithIrq { /// This initializes a non-blocking read transfer using the IRQ capabilities of the UART /// peripheral. /// @@ -735,8 +721,7 @@ impl UartWithIrqBase { self.irq_info.mode = IrqReceptionMode::Pending; self.irq_info.rx_idx = 0; self.irq_info.rx_len = max_len; - self.inner.enable_rx(); - self.inner.enable_tx(); + self.inner.enable(); self.enable_rx_irq_sources(enb_timeout_irq); unsafe { enable_interrupt(Uart::IRQ_RX) }; Ok(()) @@ -744,7 +729,7 @@ impl UartWithIrqBase { #[inline] fn enable_rx_irq_sources(&mut self, timeout: bool) { - self.inner.uart.irq_enb().modify(|_, w| { + self.inner.0.irq_enb().modify(|_, w| { if timeout { w.irq_rx_to().set_bit(); } @@ -755,30 +740,24 @@ impl UartWithIrqBase { #[inline] fn disable_rx_irq_sources(&mut self) { - self.inner.uart.irq_enb().modify(|_, w| { + self.inner.0.irq_enb().modify(|_, w| { w.irq_rx_to().clear_bit(); w.irq_rx_status().clear_bit(); w.irq_rx().clear_bit() }); } - #[inline] - pub fn enable_tx(&mut self) { - self.inner.enable_tx() - } - - #[inline] - pub fn disable_tx(&mut self) { - self.inner.disable_tx() - } - pub fn cancel_transfer(&mut self) { self.disable_rx_irq_sources(); - self.inner.clear_tx_fifo(); + self.inner.clear_fifo(); self.irq_info.rx_idx = 0; self.irq_info.rx_len = 0; } + pub fn uart(&self) -> &Uart { + &self.inner.0 + } + /// Default IRQ handler which can be used to read the packets arriving on the UART peripheral. /// /// If passed buffer is equal to or larger than the specified maximum length, an @@ -791,104 +770,102 @@ impl UartWithIrqBase { }); } let mut res = IrqResult::default(); - let mut possible_error = IrqUartError::default(); - let rx_status = self.inner.uart.rxstatus().read(); - res.raw_res = rx_status.bits(); - let irq_end = self.inner.uart.irq_end().read(); - let enb_status = self.inner.uart.enable().read(); + let irq_end = self.inner.0.irq_end().read(); + let enb_status = self.inner.0.enable().read(); let rx_enabled = enb_status.rxenable().bit_is_set(); - let _tx_enabled = enb_status.txenable().bit_is_set(); - let read_handler = |res: &mut IrqResult, - possible_error: &mut IrqUartError, - read_res: nb::Result| - -> Option { - match read_res { - Ok(byte) => Some(byte), - Err(nb::Error::WouldBlock) => None, - Err(nb::Error::Other(e)) => { - match e { - Error::Overrun => { - possible_error.overflow = true; - } - Error::FramingError => { - possible_error.framing = true; - } - Error::ParityError => { - possible_error.parity = true; - } - _ => { - res.set_result(IrqResultMask::Unknown); - } - } - None - } - } - }; + + // Half-Full interrupt. We have a guaranteed amount of data we can read. if irq_end.irq_rx().bit_is_set() { + // Determine the number of bytes to read, ensuring we leave 1 byte in the FIFO. + // We use this trick/hack because the timeout feature of the peripheral relies on data + // being in the RX FIFO. If data continues arriving, another half-full IRQ will fire. + // If not, the last byte(s) is/are emptied by the timeout interrupt. + let available_bytes = self.inner.0.rxfifoirqtrg().read().bits() as usize; + + let bytes_to_read = core::cmp::min( + available_bytes.saturating_sub(1), + self.irq_info.rx_len - self.irq_info.rx_idx, + ); + // If this interrupt bit is set, the trigger level is available at the very least. // Read everything as fast as possible - for _ in 0..core::cmp::min( - self.inner.uart.rxfifoirqtrg().read().bits() as usize, - self.irq_info.rx_len, - ) { - buf[self.irq_info.rx_idx] = (self.inner.uart.data().read().bits() & 0xff) as u8; + for _ in 0..bytes_to_read { + buf[self.irq_info.rx_idx] = (self.inner.0.data().read().bits() & 0xff) as u8; self.irq_info.rx_idx += 1; } + // On high-baudrates, data might be available immediately, and we possible have to + // read continuosly? Then again, the CPU should always be faster than that. I'd rather + // rely on the hardware firing another IRQ. I have not tried baudrates higher than + // 115200 so far. + } + let read_handler = + |possible_error: &mut IrqUartError, read_res: nb::Result| -> Option { + match read_res { + Ok(byte) => Some(byte), + Err(nb::Error::WouldBlock) => None, + Err(nb::Error::Other(e)) => { + match e { + Error::Overrun => { + possible_error.overflow = true; + } + Error::FramingError => { + possible_error.framing = true; + } + Error::ParityError => { + possible_error.parity = true; + } + _ => { + possible_error.other = true; + } + } + None + } + } + }; + // Timeout, empty the FIFO completely. + if irq_end.irq_rx_to().bit_is_set() { // While there is data in the FIFO, write it into the reception buffer loop { if self.irq_info.rx_idx == self.irq_info.rx_len { - self.irq_completion_handler(&mut res); - return Ok(res); + break; } - if let Some(byte) = read_handler(&mut res, &mut possible_error, self.inner.read()) { + if let Some(byte) = read_handler(&mut res.errors, self.inner.read()) { buf[self.irq_info.rx_idx] = byte; self.irq_info.rx_idx += 1; } else { break; } } + self.irq_completion_handler(&mut res); + return Ok(res); } // RX transfer not complete, check for RX errors if (self.irq_info.rx_idx < self.irq_info.rx_len) && rx_enabled { // Read status register again, might have changed since reading received data - let rx_status = self.inner.uart.rxstatus().read(); - res.raw_res = rx_status.bits(); + let rx_status = self.inner.0.rxstatus().read(); if rx_status.rxovr().bit_is_set() { - possible_error.overflow = true; + res.errors.overflow = true; } if rx_status.rxfrm().bit_is_set() { - possible_error.framing = true; + res.errors.framing = true; } if rx_status.rxpar().bit_is_set() { - possible_error.parity = true; - } - if rx_status.rxto().bit_is_set() { - // A timeout has occured but there might be some leftover data in the FIFO, - // so read that data as well - while let Some(byte) = - read_handler(&mut res, &mut possible_error, self.inner.read()) - { - buf[self.irq_info.rx_idx] = byte; - self.irq_info.rx_idx += 1; - } - self.irq_completion_handler(&mut res); - res.set_result(IrqResultMask::Timeout); - return Ok(res); + res.errors.parity = true; } // If it is not a timeout, it's an error - if possible_error.error() { + if res.error() { self.disable_rx_irq_sources(); - return Err(IrqError::Uart(possible_error)); + return Err(IrqError::Uart(res.errors)); } } // Clear the interrupt status bits self.inner - .uart + .0 .irq_clr() .write(|w| unsafe { w.bits(irq_end.bits()) }); Ok(res) @@ -896,48 +873,23 @@ impl UartWithIrqBase { fn irq_completion_handler(&mut self, res: &mut IrqResult) { self.disable_rx_irq_sources(); - self.inner.disable_rx(); + self.inner.disable(); res.bytes_read = self.irq_info.rx_idx; - res.clear_result(); - res.set_result(IrqResultMask::Complete); + res.complete = true; self.irq_info.mode = IrqReceptionMode::Idle; self.irq_info.rx_idx = 0; self.irq_info.rx_len = 0; } + pub fn irq_info(&self) -> &IrqInfo { + &self.irq_info + } + pub fn release(self) -> Uart { self.inner.release() } } -impl UartWithIrq { - /// See [`UartWithIrqBase::read_fixed_len_using_irq`] doc - pub fn read_fixed_len_using_irq( - &mut self, - max_len: usize, - enb_timeout_irq: bool, - ) -> Result<(), Error> { - self.base.read_fixed_len_using_irq(max_len, enb_timeout_irq) - } - - pub fn cancel_transfer(&mut self) { - self.base.cancel_transfer() - } - - /// See [`UartWithIrqBase::irq_handler`] doc - pub fn irq_handler(&mut self, buf: &mut [u8]) -> Result { - self.base.irq_handler(buf) - } - - pub fn release(self) -> (Uart, Pins) { - (self.base.release(), self.pins) - } - - pub fn downgrade(self) -> (UartWithIrqBase, Pins) { - (self.base, self.pins) - } -} - impl embedded_io::Error for Error { fn kind(&self) -> embedded_io::ErrorKind { embedded_io::ErrorKind::Other