forked from M-Labs/zynq-rs
DevC driver refactored.
This commit is contained in:
parent
a17a5d2925
commit
d52466cacf
@ -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<F: Fn(&mut Self) -> 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()
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user