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 crate::slcr;
|
||||||
|
use embedded_hal::timer::CountDown;
|
||||||
|
use libcortex_a9::cache;
|
||||||
use libregister::*;
|
use libregister::*;
|
||||||
use log::debug;
|
use log::{debug, trace};
|
||||||
|
|
||||||
mod regs;
|
mod regs;
|
||||||
|
|
||||||
pub struct DevC {
|
pub struct DevC {
|
||||||
regs: &'static mut regs::RegisterBlock,
|
regs: &'static mut regs::RegisterBlock,
|
||||||
|
enabled: bool,
|
||||||
|
count_down: super::timer::global::CountDown,
|
||||||
|
timeout_ms: Milliseconds,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// DMA transfer type for PCAP
|
/// DMA transfer type for PCAP
|
||||||
/// All insecure, we do not implement encrypted transfer
|
/// All insecure, we do not implement encrypted transfer
|
||||||
#[derive(PartialEq)]
|
#[derive(PartialEq, Clone, Copy)]
|
||||||
pub enum TransferType {
|
pub enum TransferType {
|
||||||
PcapWrite,
|
PcapWrite,
|
||||||
PcapReadback,
|
PcapReadback,
|
||||||
ConcurrentReadWrite,
|
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 {
|
impl DevC {
|
||||||
|
/// Create a new DevC peripheral handle with default timeout = 500ms.
|
||||||
pub fn new() -> Self {
|
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 {
|
DevC {
|
||||||
regs: regs::RegisterBlock::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) {
|
pub fn enable(&mut self) {
|
||||||
|
const UNLOCK_PATTERN: u32 = 0x757BDF0D;
|
||||||
unsafe {
|
unsafe {
|
||||||
// unlock register with magic pattern
|
// unlock register with magic pattern
|
||||||
self.regs.unlock.write(0x757BDF0D);
|
self.regs.unlock.write(UNLOCK_PATTERN);
|
||||||
}
|
}
|
||||||
self.regs
|
self.regs
|
||||||
.control
|
.control
|
||||||
|
@ -37,20 +96,55 @@ impl DevC {
|
||||||
.int_mask
|
.int_mask
|
||||||
.write(self::regs::int_mask::Write { inner: 0xFFFFFFFF });
|
.write(self::regs::int_mask::Write { inner: 0xFFFFFFFF });
|
||||||
self.clear_interrupts();
|
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) {
|
pub fn disable(&mut self) {
|
||||||
self.regs
|
self.regs
|
||||||
.control
|
.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 {
|
pub fn is_done(&self) -> bool {
|
||||||
// Note: contrary to what the TRM says, this appears to be simply
|
// Note: contrary to what the TRM says, this appears to be simply the
|
||||||
// the state of the DONE signal.
|
// state of the DONE signal.
|
||||||
self.regs.int_sts.read().ixr_pcfg_done()
|
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");
|
debug!("Init preload FPGA");
|
||||||
slcr::RegisterBlock::unlocked(|slcr| {
|
slcr::RegisterBlock::unlocked(|slcr| {
|
||||||
slcr.init_preload_fpga();
|
slcr.init_preload_fpga();
|
||||||
|
@ -59,42 +153,70 @@ impl DevC {
|
||||||
// set PCFG_PROG_B to high low high
|
// 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(true));
|
||||||
self.regs.control.modify(|_, w| w.pcfg_prog_b(false));
|
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));
|
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.int_sts.write(
|
||||||
self::regs::IntSts::zeroed()
|
self::regs::IntSts::zeroed()
|
||||||
.pss_cfg_reset_b_int(true)
|
.pss_cfg_reset_b_int(true)
|
||||||
.ixr_pcfg_cfg_rst(true),
|
.ixr_pcfg_cfg_rst(true),
|
||||||
);
|
);
|
||||||
|
|
||||||
debug!("ADDR: {:0X}", src_addr);
|
|
||||||
self.dma_transfer(
|
self.dma_transfer(
|
||||||
src_addr | 0x1,
|
TransferTarget::SliceSrcLast(src),
|
||||||
INVALID_ADDR | 0x1,
|
TransferTarget::PL(src.len() as u32),
|
||||||
len,
|
|
||||||
len,
|
|
||||||
TransferType::PcapWrite,
|
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");
|
debug!("Waiting for done");
|
||||||
while !self.is_done() {}
|
self.wait_condition(|s| s.is_done(), DevcError::DoneTimeout)?;
|
||||||
|
|
||||||
debug!("INT_STS: {:0X}", self.regs.int_sts.read().inner);
|
|
||||||
|
|
||||||
debug!("Init postload FPGA");
|
debug!("Init postload FPGA");
|
||||||
slcr::RegisterBlock::unlocked(|slcr| {
|
slcr::RegisterBlock::unlocked(|slcr| {
|
||||||
slcr.init_postload_fpga();
|
slcr.init_postload_fpga();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Initiate DMA transaction, all lengths are in words.
|
/// Initiate DMA transaction
|
||||||
/// > This function is not meant to be used directly.
|
/// This function only sets the src and dest registers, and should not be used directly.
|
||||||
fn initiate_dma(&mut self, src_addr: u32, dest_addr: u32, src_len: u32, dest_len: u32) {
|
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_src_addr.modify(|_, w| w.src_addr(src_addr));
|
||||||
self.regs
|
self.regs
|
||||||
.dma_dest_addr
|
.dma_dest_addr
|
||||||
|
@ -103,34 +225,44 @@ impl DevC {
|
||||||
self.regs.dma_dest_len.modify(|_, w| w.dma_len(dest_len));
|
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(
|
pub fn dma_transfer(
|
||||||
&mut self,
|
&mut self,
|
||||||
src_addr: u32,
|
src: TransferTarget,
|
||||||
dest_addr: u32,
|
dest: TransferTarget,
|
||||||
src_len: u32,
|
|
||||||
dest_len: u32,
|
|
||||||
transfer_type: TransferType,
|
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() {
|
if self.regs.status.read().dma_cmd_q_f() {
|
||||||
return Err("DMA busy");
|
return Err(DevcError::DmaBusy);
|
||||||
}
|
}
|
||||||
|
|
||||||
if transfer_type != TransferType::ConcurrentReadWrite
|
if transfer_type != TransferType::ConcurrentReadWrite
|
||||||
&& !self.regs.status.read().pcfg_init()
|
&& !self.regs.status.read().pcfg_init()
|
||||||
{
|
{
|
||||||
return Err("Fabric not initialized");
|
return Err(DevcError::NotInitialized);
|
||||||
}
|
}
|
||||||
match &transfer_type {
|
match &transfer_type {
|
||||||
TransferType::PcapReadback => {
|
TransferType::PcapReadback => {
|
||||||
// clear internal PCAP loopback
|
// clear internal PCAP loopback
|
||||||
self.regs.mctrl.modify(|_, w| w.pcap_lpbk(false));
|
self.regs.mctrl.modify(|_, w| w.pcap_lpbk(false));
|
||||||
// send READ frame command
|
// 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
|
// wait until DMA done
|
||||||
while !self.regs.int_sts.read().ixr_d_p_done() {}
|
self.wait_dma_transfer_complete()?;
|
||||||
// initiate the DMA write
|
// 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 => {
|
TransferType::PcapWrite | TransferType::ConcurrentReadWrite => {
|
||||||
self.regs
|
self.regs
|
||||||
|
@ -139,26 +271,34 @@ impl DevC {
|
||||||
// PCAP data transmitted every clock
|
// PCAP data transmitted every clock
|
||||||
self.regs.control.modify(|_, w| w.pcap_rate_en(false));
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn wait_dma_transfer_complete(&mut self) {
|
fn wait_dma_transfer_complete(&mut self) -> Result<(), DevcError> {
|
||||||
debug!("Wait for DMA done");
|
trace!("Wait for DMA done");
|
||||||
while !self.regs.int_sts.read().ixr_dma_done() {}
|
self.wait_condition(
|
||||||
|
|s| s.regs.int_sts.read().ixr_dma_done(),
|
||||||
|
DevcError::DmaTimeout,
|
||||||
|
)?;
|
||||||
self.regs
|
self.regs
|
||||||
.int_sts
|
.int_sts
|
||||||
.write(self::regs::IntSts::zeroed().ixr_dma_done(true));
|
.write(self::regs::IntSts::zeroed().ixr_dma_done(true));
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Dump useful registers for devc block.
|
||||||
pub fn dump_registers(&self) {
|
pub fn dump_registers(&self) {
|
||||||
|
debug!("Mctrl: 0x{:0X}", self.regs.mctrl.read().inner);
|
||||||
debug!("Control: 0x{:0X}", self.regs.control.read().inner);
|
debug!("Control: 0x{:0X}", self.regs.control.read().inner);
|
||||||
debug!("Status: 0x{:0X}", self.regs.status.read().inner);
|
debug!("Status: 0x{:0X}", self.regs.status.read().inner);
|
||||||
debug!("INT STS: 0x{:0X}", self.regs.int_sts.read().inner);
|
debug!("INT STS: 0x{:0X}", self.regs.int_sts.read().inner);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Clear interrupt status for devc.
|
||||||
pub fn clear_interrupts(&mut self) {
|
pub fn clear_interrupts(&mut self) {
|
||||||
self.regs.int_sts.modify(|_, w| {
|
self.regs.int_sts.modify(|_, w| {
|
||||||
w.pss_gts_usr_b_int(true)
|
w.pss_gts_usr_b_int(true)
|
||||||
|
@ -187,4 +327,16 @@ impl DevC {
|
||||||
.ixr_pcfg_init_ne(true)
|
.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