From d52466cacf0eed915076d24854194216dd4273bb Mon Sep 17 00:00:00 2001 From: pca006132 Date: Tue, 16 Jun 2020 14:55:53 +0800 Subject: [PATCH] DevC driver refactored. --- libboard_zynq/src/devc/mod.rs | 234 ++++++++++++++++++++++++++++------ 1 file changed, 193 insertions(+), 41 deletions(-) diff --git a/libboard_zynq/src/devc/mod.rs b/libboard_zynq/src/devc/mod.rs index 4a954cb..1363525 100644 --- a/libboard_zynq/src/devc/mod.rs +++ b/libboard_zynq/src/devc/mod.rs @@ -1,34 +1,93 @@ +use super::clocks::Clocks; +use super::time::Milliseconds; use crate::slcr; +use embedded_hal::timer::CountDown; +use libcortex_a9::cache; use libregister::*; -use log::debug; +use log::{debug, trace}; + mod regs; pub struct DevC { regs: &'static mut regs::RegisterBlock, + enabled: bool, + count_down: super::timer::global::CountDown, + timeout_ms: Milliseconds, } /// DMA transfer type for PCAP /// All insecure, we do not implement encrypted transfer -#[derive(PartialEq)] +#[derive(PartialEq, Clone, Copy)] pub enum TransferType { PcapWrite, PcapReadback, ConcurrentReadWrite, } -pub const INVALID_ADDR: u32 = 0xFFFFFFFF; +pub enum TransferTarget<'a> { + /// From/To PL, with length in bytes. + PL(u32), + /// Source target, immutable. + SliceSrc(&'a [u8]), + /// Last source target, immutable. + SliceSrcLast(&'a [u8]), + /// Destination target, mutable. + SliceDest(&'a mut [u8]), + /// Last destination target, mutable. + SliceDestLast(&'a mut [u8]), +} + +#[derive(PartialEq, Clone, Copy, Debug)] +pub enum DevcError { + NotInitialized, + ResetTimeout, + DmaBusy, + DmaTimeout, + DoneTimeout, + Unknown(u32), +} + +impl core::fmt::Display for DevcError { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + use DevcError::*; + match self { + NotInitialized => write!(f, "DevC driver not initialized properly."), + ResetTimeout => write!(f, "DevC driver reset timeout."), + DmaBusy => write!(f, "DevC driver DMA busy."), + DmaTimeout => write!(f, "DevC driver DMA timeout."), + DoneTimeout => write!( + f, + "FPGA DONE signal timeout. Check if the bitstream is correct." + ), + Unknown(reg) => write!(f, "Unknown error, interrupt status register = 0x{:0X}", reg), + } + } +} impl DevC { + /// Create a new DevC peripheral handle with default timeout = 500ms. pub fn new() -> Self { + Self::new_timeout(Milliseconds(500)) + } + + /// Create a new DevC peripheral handle. + /// `timeout_ms`: timeout for operations like initialize and DMA transfer. + pub fn new_timeout(timeout_ms: Milliseconds) -> Self { DevC { regs: regs::RegisterBlock::devc(), + enabled: false, + count_down: super::timer::GlobalTimer::start().countdown(), + timeout_ms, } } + /// Enable the devc driver, must be called before `program` or + /// `start_dma_transaction`. pub fn enable(&mut self) { + const UNLOCK_PATTERN: u32 = 0x757BDF0D; unsafe { // unlock register with magic pattern - self.regs.unlock.write(0x757BDF0D); + self.regs.unlock.write(UNLOCK_PATTERN); } self.regs .control @@ -37,20 +96,55 @@ impl DevC { .int_mask .write(self::regs::int_mask::Write { inner: 0xFFFFFFFF }); self.clear_interrupts(); + self.enabled = true; } + + /// Disable the devc driver. + /// `enable` has to be called before further `program` or + /// `start_dma_transaction`. pub fn disable(&mut self) { self.regs .control - .modify(|_, w| w.pcap_mode(false).pcap_pr(false)) + .modify(|_, w| w.pcap_mode(false).pcap_pr(false)); + self.enabled = false; } + /// Check if the FPGA programming is done. pub fn is_done(&self) -> bool { - // Note: contrary to what the TRM says, this appears to be simply - // the state of the DONE signal. + // Note: contrary to what the TRM says, this appears to be simply the + // state of the DONE signal. self.regs.int_sts.read().ixr_pcfg_done() } - pub fn program(&mut self, src_addr: u32, len: u32) { + /// Wait on a certain condition with hardcoded timeout. + fn wait_condition bool>( + &mut self, + fun: F, + err: DevcError, + ) -> Result<(), DevcError> { + self.count_down.start(self.timeout_ms); + while let Err(nb::Error::WouldBlock) = self.count_down.wait() { + if fun(self) { + return Ok(()); + } else if self.has_error() { + return Err(DevcError::Unknown(self.regs.int_sts.read().inner)); + } + } + Err(err) + } + + /// Program the FPGA. + /// Note that the user should make sure that the bitstream loaded is + /// correct. + pub fn program(&mut self, src: &[u8]) -> Result<(), DevcError> { + if !self.enabled { + panic!("Attempting to use devc when it is not enabled"); + } + self.clear_interrupts(); + + debug!("Invalidate DCache for bitstream buffer"); + cache::dcci_slice(src); + debug!("Init preload FPGA"); slcr::RegisterBlock::unlocked(|slcr| { slcr.init_preload_fpga(); @@ -59,42 +153,70 @@ impl DevC { // set PCFG_PROG_B to high low high self.regs.control.modify(|_, w| w.pcfg_prog_b(true)); self.regs.control.modify(|_, w| w.pcfg_prog_b(false)); - while self.regs.status.read().pcfg_init() {} + + // wait until init is false + self.wait_condition( + |s| !s.regs.status.read().pcfg_init(), + DevcError::ResetTimeout, + )?; + self.regs.control.modify(|_, w| w.pcfg_prog_b(true)); - while !self.regs.status.read().pcfg_init() {} + // wait until init is true + self.wait_condition( + |s| s.regs.status.read().pcfg_init(), + DevcError::ResetTimeout, + )?; + self.regs.int_sts.write( self::regs::IntSts::zeroed() .pss_cfg_reset_b_int(true) .ixr_pcfg_cfg_rst(true), ); - debug!("ADDR: {:0X}", src_addr); self.dma_transfer( - src_addr | 0x1, - INVALID_ADDR | 0x1, - len, - len, + TransferTarget::SliceSrcLast(src), + TransferTarget::PL(src.len() as u32), TransferType::PcapWrite, - ); - - self.wait_dma_transfer_complete(); - debug!("INT_STS: {:0X}", self.regs.int_sts.read().inner); - debug!("STATUS: {:0X}", self.regs.status.read().inner); + )?; debug!("Waiting for done"); - while !self.is_done() {} - - debug!("INT_STS: {:0X}", self.regs.int_sts.read().inner); + self.wait_condition(|s| s.is_done(), DevcError::DoneTimeout)?; debug!("Init postload FPGA"); slcr::RegisterBlock::unlocked(|slcr| { slcr.init_postload_fpga(); }); + + Ok(()) } - /// Initiate DMA transaction, all lengths are in words. - /// > This function is not meant to be used directly. - fn initiate_dma(&mut self, src_addr: u32, dest_addr: u32, src_len: u32, dest_len: u32) { + /// Initiate DMA transaction + /// This function only sets the src and dest registers, and should not be used directly. + fn initiate_dma(&mut self, src: TransferTarget, dest: TransferTarget) { + use TransferTarget::*; + const INVALID_ADDR: u32 = 0xFFFFFFFF; + + if let (PL(_), PL(_)) = (&src, &dest) { + panic!("Only one of src/dest can be PL"); + } + + let (src_addr, src_len): (u32, u32) = match src { + PL(l) => (INVALID_ADDR, l / 4), + SliceSrc(s) => (s.as_ptr() as u32, s.len() as u32 / 4), + SliceDest(s) => (s.as_ptr() as u32, s.len() as u32 / 4), + SliceSrcLast(s) => ((s.as_ptr() as u32) | 0x01, s.len() as u32 / 4), + SliceDestLast(s) => ((s.as_ptr() as u32) | 0x01, s.len() as u32 / 4), + }; + + let (dest_addr, dest_len): (u32, u32) = match dest { + PL(l) => (INVALID_ADDR, l / 4), + SliceDest(s) => (s.as_ptr() as u32, s.len() as u32 / 4), + SliceDestLast(s) => ((s.as_ptr() as u32) | 0x01, s.len() as u32 / 4), + SliceSrc(_) | SliceSrcLast(_) => { + panic!("Destination cannot be SliceSrc/SliceSrcLast, it must be mutable.") + } + }; + self.regs.dma_src_addr.modify(|_, w| w.src_addr(src_addr)); self.regs .dma_dest_addr @@ -103,34 +225,44 @@ impl DevC { self.regs.dma_dest_len.modify(|_, w| w.dma_len(dest_len)); } - /// DMA transfer, all lengths are in words. + /// Blocking DMA transfer + /// ## Note + /// This is blocking because there seems to be no other way to guarantee + /// safety, and I don't think requiring static is a solution here due to the + /// large buffer size. + /// See https://docs.rust-embedded.org/embedonomicon/dma.html for details. + /// + /// The following checks are implemented in runtime (panic). + /// * Dest would *NOT* accept src type, as the slices are immutable. + /// * At most one of src and dest can be PL type. pub fn dma_transfer( &mut self, - src_addr: u32, - dest_addr: u32, - src_len: u32, - dest_len: u32, + src: TransferTarget, + dest: TransferTarget, transfer_type: TransferType, - ) -> Result<(), &str> { + ) -> Result<(), DevcError> { + if !self.enabled { + panic!("Attempting to use devc when it is not enabled"); + } if self.regs.status.read().dma_cmd_q_f() { - return Err("DMA busy"); + return Err(DevcError::DmaBusy); } if transfer_type != TransferType::ConcurrentReadWrite && !self.regs.status.read().pcfg_init() { - return Err("Fabric not initialized"); + return Err(DevcError::NotInitialized); } match &transfer_type { TransferType::PcapReadback => { // clear internal PCAP loopback self.regs.mctrl.modify(|_, w| w.pcap_lpbk(false)); // send READ frame command - self.initiate_dma(src_addr, INVALID_ADDR, src_len, 0); + self.initiate_dma(src, TransferTarget::PL(0)); // wait until DMA done - while !self.regs.int_sts.read().ixr_d_p_done() {} + self.wait_dma_transfer_complete()?; // initiate the DMA write - self.initiate_dma(INVALID_ADDR, dest_addr, 0, dest_len); + self.initiate_dma(TransferTarget::PL(0), dest); } TransferType::PcapWrite | TransferType::ConcurrentReadWrite => { self.regs @@ -139,26 +271,34 @@ impl DevC { // PCAP data transmitted every clock self.regs.control.modify(|_, w| w.pcap_rate_en(false)); - self.initiate_dma(src_addr, dest_addr, src_len, dest_len); + self.initiate_dma(src, dest); } } + self.wait_dma_transfer_complete()?; Ok(()) } - pub fn wait_dma_transfer_complete(&mut self) { - debug!("Wait for DMA done"); - while !self.regs.int_sts.read().ixr_dma_done() {} + fn wait_dma_transfer_complete(&mut self) -> Result<(), DevcError> { + trace!("Wait for DMA done"); + self.wait_condition( + |s| s.regs.int_sts.read().ixr_dma_done(), + DevcError::DmaTimeout, + )?; self.regs .int_sts .write(self::regs::IntSts::zeroed().ixr_dma_done(true)); + Ok(()) } + /// Dump useful registers for devc block. pub fn dump_registers(&self) { + debug!("Mctrl: 0x{:0X}", self.regs.mctrl.read().inner); debug!("Control: 0x{:0X}", self.regs.control.read().inner); debug!("Status: 0x{:0X}", self.regs.status.read().inner); debug!("INT STS: 0x{:0X}", self.regs.int_sts.read().inner); } + /// Clear interrupt status for devc. pub fn clear_interrupts(&mut self) { self.regs.int_sts.modify(|_, w| { w.pss_gts_usr_b_int(true) @@ -187,4 +327,16 @@ impl DevC { .ixr_pcfg_init_ne(true) }) } + + fn has_error(&self) -> bool { + let status = self.regs.int_sts.read(); + status.ixr_axi_wto() + || status.ixr_axi_werr() + || status.ixr_axi_rto() + || status.ixr_axi_rerr() + || status.ixr_rx_fifo_ov() + || status.ixr_dma_cmd_err() + || status.ixr_dma_q_ov() + || status.ixr_p2d_len_err() + } }