Skip to content

Commit

Permalink
Merge pull request #603 from betrusted-io/cram-dev
Browse files Browse the repository at this point in the history
Merge cram-dev WIP to avoid branch divergence & check CI status
  • Loading branch information
bunnie authored Dec 12, 2024
2 parents 946c0f7 + 5d35dee commit bd3e99c
Show file tree
Hide file tree
Showing 14 changed files with 6,498 additions and 995 deletions.
9 changes: 8 additions & 1 deletion RELEASE-v0.9.md
Original file line number Diff line number Diff line change
Expand Up @@ -484,7 +484,14 @@ perform the Xous firmware upgrade. This requires running manual update commands,
- Mailbox protocol to other devices has been tested, working.
- TRNG has been tuned, partially validated.
- BIO-BDMA test cases added
- Various fixes to track changes in Rust 1.80
- Full USB mass storage stack added in loader mode
- Interactive USB updates via loader mode
- Camera driver with live preview
- QR code decoding (up to version 8)
- mini-gfx:
- New graphics crate for small screen targets that does not require windows or borders. Drops the GAM, condenses APIs together for smaller memory footprint devices.
- Very much a WIP
- Various fixes to track changes in Rust 1.80/1.81/1.82/1.83
- Add documentation to the `modals` library (thanks @rowr111)
- Due to a breaking change in Renode, this release is only compatible with Renode equal to or later than 1.15.2.7965(e6e79aad-202408180425) (see issue #570 / PR #572)
- Migrate to `rkyv` v0.8+.
Expand Down
4 changes: 3 additions & 1 deletion libs/cramium-hal/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ board-baosec = [
board-baosor = ["camera-ov2640", "axp2101"] # Precursor form factor
board-dabao = [] # Dev board form factor
verilator-only = []
mpw = []
hdl-test = []

udma-stress-test = [
] # For RTL simulation testing - aggravates a key corner case
Expand All @@ -47,4 +49,4 @@ compress-entropy = []
magic-manual = []
std = ["log", "xous-api-names", "usb-device", "xous-api-ticktimer"]
derive-rkyv = ["rkyv"]
default = []
default = ["hdl-test"]
2 changes: 2 additions & 0 deletions libs/cramium-hal/src/ov2640/ov2640.rs
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,8 @@ impl Ov2640 {
/// TODO: figure out how to length-bound this to...the frame slice size? line size? idk...
pub unsafe fn rx_buf_phys<T: UdmaWidths>(&self) -> &[T] { &self.ifram.as_ref().unwrap().as_phys_slice() }

/// TODO: Rework this to use the frame sync + automatic re-initiation on capture_await() for frames
/// TODO: Also make an interrupt driven version of this.
pub fn capture_async(&mut self) {
// we want the sliced resolution so resolve resolution through the method call wrapper
let (cols, rows) = self.resolution();
Expand Down
168 changes: 151 additions & 17 deletions libs/cramium-hal/src/udma/i2c.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,19 @@ use crate::ifram::IframRange;
use crate::udma::*;
use crate::udma::{Bank, Udma};

#[cfg(not(feature = "hdl-test"))]
const TIMEOUT_ITERS: usize = 1_000_000;
#[cfg(feature = "hdl-test")]
const TIMEOUT_ITERS: usize = 5000;

// MPW had this register:
// pub const REG_SETUP: crate::Register = crate::Register::new(13, 0x1);
// pub const REG_SETUP_R_DO_RST: crate::Field = crate::Field::new(1, 0, REG_SETUP);
// It is gone in NTO and we use the UDMA system reset instead
#[cfg(feature = "mpw")]
const SETUP_OFFSET: usize = 13;
#[cfg(feature = "mpw")]
const DO_RST_MASK: usize = 1;

#[derive(Copy, Clone)]
#[repr(u8)]
Expand Down Expand Up @@ -75,9 +87,12 @@ const MAX_I2C_TXLEN: usize = 512;
const MAX_I2C_RXLEN: usize = 512;
const MAX_I2C_CMDLEN: usize = 512;

pub struct I2c {
pub struct I2c<'a> {
csr: CSR<u32>,
_channel: I2cChannel,
#[allow(dead_code)] // used in NTO
udma_global: &'a dyn UdmaGlobalConfig,
#[allow(dead_code)] // used in NTO
channel: I2cChannel,
divider: u16,
perclk_freq: u32,
pub ifram: IframRange,
Expand All @@ -91,20 +106,25 @@ pub struct I2c {
pending: I2cPending,
}

impl Udma for I2c {
impl Udma for I2c<'_> {
fn csr_mut(&mut self) -> &mut CSR<u32> { &mut self.csr }

fn csr(&self) -> &CSR<u32> { &self.csr }
}

impl I2c {
impl<'a> I2c<'a> {
/// Safety: called only after global clock for I2C channel is enabled.
/// It is also unsafe to `Drop` because you have to cleanup the clock manually.
#[cfg(feature = "std")]
pub unsafe fn new(channel: I2cChannel, i2c_freq: u32, perclk_freq: u32) -> Option<Self> {
pub unsafe fn new(
channel: I2cChannel,
i2c_freq: u32,
perclk_freq: u32,
udma_global: &'a dyn UdmaGlobalConfig,
) -> Option<Self> {
// one page is the minimum size we can request
if let Some(ifram) = IframRange::request(4096, None) {
Some(I2c::new_with_ifram(channel, i2c_freq, perclk_freq, ifram))
Some(I2c::new_with_ifram(channel, i2c_freq, perclk_freq, ifram, udma_global))
} else {
None
}
Expand All @@ -115,6 +135,7 @@ impl I2c {
i2c_freq: u32,
perclk_freq: u32,
ifram: IframRange,
udma_global: &'a dyn UdmaGlobalConfig,
) -> Self {
// divide-by-4 is an empirical observation
let divider: u16 = ((((perclk_freq / 2) / i2c_freq) / 4).min(u16::MAX as u32)) as u16;
Expand All @@ -134,18 +155,33 @@ impl I2c {
)
.expect("couldn't map i2c port");
#[cfg(target_os = "xous")]
#[allow(unused_mut)] // because it is used when `mpw` feature is selected
let mut csr = CSR::new(csr_range.as_mut_ptr() as *mut u32);
#[cfg(not(target_os = "xous"))]
#[allow(unused_mut)] // because it is used when `mpw` feature is selected
let mut csr = CSR::new(base_addr as *mut u32);
// reset the block
csr.wfo(utra::udma_i2c_0::REG_SETUP_R_DO_RST, 1);
csr.wo(utra::udma_i2c_0::REG_SETUP, 0);
// reset the block, if MPW. If NTO, this is handled by udma global
#[cfg(feature = "mpw")]
unsafe {
csr.base().add(SETUP_OFFSET).write_volatile(DO_RST_MASK as u32);
csr.base().add(SETUP_OFFSET).write_volatile(0);
}
#[cfg(not(feature = "mpw"))]
{
udma_global.reset(match channel {
I2cChannel::Channel0 => PeriphId::I2c0,
I2cChannel::Channel1 => PeriphId::I2c1,
I2cChannel::Channel2 => PeriphId::I2c2,
I2cChannel::Channel3 => PeriphId::I2c3,
});
}
// one page is the minimum size we can request
let ifram_base = ifram.virt_range.as_ptr() as usize;
let ifram_base_phys = ifram.phys_range.as_ptr() as usize;
let mut i2c = I2c {
csr,
_channel: channel,
udma_global,
channel,
ifram,
divider,
cmd_buf: unsafe { core::slice::from_raw_parts_mut(ifram_base as *mut u32, MAX_I2C_CMDLEN) },
Expand Down Expand Up @@ -221,8 +257,22 @@ impl I2c {
self.udma_reset(Bank::Custom);
self.udma_reset(Bank::Tx);
self.udma_reset(Bank::Rx);
self.csr.wfo(utra::udma_i2c_0::REG_SETUP_R_DO_RST, 1);
self.csr.wo(utra::udma_i2c_0::REG_SETUP, 0);
// reset the block, if MPW. If NTO, this needs to be handled by the upper level code with a
// reset to udma_global
#[cfg(feature = "mpw")]
unsafe {
self.csr.base().add(SETUP_OFFSET).write_volatile(DO_RST_MASK as u32);
self.csr.base().add(SETUP_OFFSET).write_volatile(0);
}
#[cfg(not(feature = "mpw"))]
{
self.udma_global.reset(match self.channel {
I2cChannel::Channel0 => PeriphId::I2c0,
I2cChannel::Channel1 => PeriphId::I2c1,
I2cChannel::Channel2 => PeriphId::I2c2,
I2cChannel::Channel3 => PeriphId::I2c3,
});
}

self.send_cmd_list(&[I2cCmd::Config(self.divider)]);
self.pending.take();
Expand All @@ -236,7 +286,10 @@ impl I2c {
let ret = match self.pending.take() {
I2cPending::Read(len) => {
if let Some(buf) = rx_buf {
#[cfg(feature = "mpw")]
buf[..len].copy_from_slice(&self.rx_buf[3..3 + len]);
#[cfg(not(feature = "mpw"))]
buf[..len].copy_from_slice(&self.rx_buf[..len]);
Ok(len)
} else {
// the pending transaction was a read, but the user did not call us
Expand All @@ -250,14 +303,66 @@ impl I2c {
ret
}

pub fn reset(&mut self) {
// reset the block
self.udma_reset(Bank::Custom);
self.udma_reset(Bank::Tx);
self.udma_reset(Bank::Rx);
// reset the block, if MPW. If NTO, this needs to be handled by the upper level code with a
// reset to udma_global
#[cfg(feature = "mpw")]
unsafe {
self.csr.base().add(SETUP_OFFSET).write_volatile(DO_RST_MASK as u32);
self.csr.base().add(SETUP_OFFSET).write_volatile(0);
}
#[cfg(not(feature = "mpw"))]
{
self.udma_global.reset(match self.channel {
I2cChannel::Channel0 => PeriphId::I2c0,
I2cChannel::Channel1 => PeriphId::I2c1,
I2cChannel::Channel2 => PeriphId::I2c2,
I2cChannel::Channel3 => PeriphId::I2c3,
});
}

self.send_cmd_list(&[I2cCmd::Config(self.divider)]);
self.pending.take();
}

fn busy(&self) -> bool {
self.csr.rf(utra::udma_i2c_0::REG_STATUS_R_BUSY) != 0
|| self.csr.rf(utra::udma_i2c_0::REG_STATUS_R_BUSY) != 0
|| self.udma_busy(Bank::Custom)
|| self.udma_busy(Bank::Tx)
|| self.udma_busy(Bank::Rx)
}

/// Basically, does a read from an I2C address without specifying a register. The protocol
/// would not make sense for any real-world application, but it is an MVP than exercises
/// both a write (to specify the device address) and a read (the response data) form of
/// the I2C PHY protocol.
#[cfg(feature = "hdl-test")]
#[allow(dead_code)]
pub fn i2c_hdl_test(&mut self, byte: u8) -> Result<u8, xous::Error> {
self.new_tranaction();
self.push_cmd(I2cCmd::Config(self.divider));
self.push_cmd(I2cCmd::Start);
self.push_cmd(I2cCmd::WriteByte(byte << 1 | 0x1)); // issue a read
self.push_cmd(I2cCmd::RdNack);
self.push_cmd(I2cCmd::Stop);

// safety: this is safe because the cmd_buf_phys() slice is passed to a function that only
// uses it as a base/bounds reference and it will not actually access the data.
unsafe {
self.udma_enqueue(Bank::Rx, &self.rx_buf_phys[..1], CFG_EN);
self.udma_enqueue(Bank::Custom, &self.cmd_buf_phys[..self.seq_len], CFG_EN);
}
self.pending = I2cPending::Read(1);

let mut rx = [0u8];
self.i2c_await(Some(&mut rx), false)?;
Ok(rx[0])
}

pub fn i2c_write_async(&mut self, dev: u8, adr: u8, data: &[u8]) -> Result<usize, xous::Error> {
// The implementation of this is gross because we have to stuff the command list
assert!(data.len() < 256); // is is a conservative bound, the limit is due to the cmd buf length limit
Expand All @@ -280,6 +385,13 @@ impl I2c {
self.udma_enqueue(Bank::Tx, &self.tx_buf_phys[..data.len()], CFG_EN);
self.udma_enqueue(Bank::Custom, &self.cmd_buf_phys[..self.seq_len], CFG_EN);
}
// wait for the commands to propagate before returning
#[cfg(not(feature = "mpw"))]
while self.csr.rf(utra::udma_i2c_0::REG_STATUS_R_BUSY) == 0
|| self.csr.rf(utra::udma_i2c_0::REG_CMD_SIZE_R_CMD_SIZE) != 0
{}
#[cfg(feature = "mpw")]
while self.csr.rf(utra::udma_i2c_0::REG_STATUS_R_BUSY) == 0 {}
self.pending = I2cPending::Write(data.len());
Ok(data.len())
}
Expand All @@ -297,8 +409,20 @@ impl I2c {
// block has to be reset on every start transaction due to a... bug? programming error?
// where the Rx length is mismatched from the actual length of Rx data expected because
// it seems the Rx buffer pointer increments even during Tx events.
self.csr.wfo(utra::udma_i2c_0::REG_SETUP_R_DO_RST, 1);
self.csr.wo(utra::udma_i2c_0::REG_SETUP, 0);
#[cfg(feature = "mpw")]
unsafe {
self.csr.base().add(SETUP_OFFSET).write_volatile(DO_RST_MASK as u32);
self.csr.base().add(SETUP_OFFSET).write_volatile(0);
}
#[cfg(not(feature = "mpw"))]
{
self.udma_global.reset(match self.channel {
I2cChannel::Channel0 => PeriphId::I2c0,
I2cChannel::Channel1 => PeriphId::I2c1,
I2cChannel::Channel2 => PeriphId::I2c2,
I2cChannel::Channel3 => PeriphId::I2c3,
});
}

// into the pre-allocated Tx buf
self.new_tranaction();
Expand All @@ -319,16 +443,26 @@ impl I2c {
// safety: this is safe because the cmd_buf_phys() slice is passed to a function that only
// uses it as a base/bounds reference and it will not actually access the data.
unsafe {
// the extra 3 are dummy bytes that were received while the address was being set up
// the extra 3 are dummy bytes that were received while the address was being set up - MPW bug
#[cfg(feature = "mpw")]
self.udma_enqueue(Bank::Rx, &self.rx_buf_phys[..len + 3], CFG_EN);
#[cfg(not(feature = "mpw"))]
self.udma_enqueue(Bank::Rx, &self.rx_buf_phys[..len], CFG_EN);
self.udma_enqueue(Bank::Custom, &self.cmd_buf_phys[..self.seq_len], CFG_EN);
}
// wait for the commands to propagate before returning
#[cfg(not(feature = "mpw"))]
while self.csr.rf(utra::udma_i2c_0::REG_STATUS_R_BUSY) == 0
|| self.csr.rf(utra::udma_i2c_0::REG_CMD_SIZE_R_CMD_SIZE) != 0
{}
#[cfg(feature = "mpw")]
while self.csr.rf(utra::udma_i2c_0::REG_STATUS_R_BUSY) == 0 {}
self.pending = I2cPending::Read(len);
Ok(len)
}
}

impl I2cApi for I2c {
impl I2cApi for I2c<'_> {
fn i2c_read(
&mut self,
dev: u8,
Expand Down
18 changes: 18 additions & 0 deletions libs/cramium-hal/src/udma/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ use crate::SharedCsr;
enum GlobalReg {
ClockGate = 0,
EventIn = 1,
Reset = 2,
}
impl Into<usize> for GlobalReg {
fn into(self) -> usize { self as usize }
Expand Down Expand Up @@ -241,6 +242,7 @@ pub trait UdmaGlobalConfig {
event_type: PeriphEventType,
to_channel: EventChannel,
);
fn reset(&self, peripheral: PeriphId);
}

#[repr(u32)]
Expand Down Expand Up @@ -319,6 +321,20 @@ impl GlobalConfig {
// Safety: only safe when used in the context of UDMA registers.
unsafe { self.csr.base().add(GlobalReg::EventIn.into()).read_volatile() }
}

pub fn reset(&self, peripheral: PeriphId) {
unsafe {
// assert reset
self.csr.base().add(GlobalReg::Reset.into()).write_volatile(peripheral.into());
// a few nops for the reset to propagate
core::arch::asm!(
"nop", "nop", "nop", "nop", "nop", "nop", "nop", "nop", "nop", "nop", "nop", "nop", "nop",
"nop", "nop", "nop",
);
// de-assert reset
self.csr.base().add(GlobalReg::Reset.into()).write_volatile(0);
}
}
}

impl UdmaGlobalConfig for GlobalConfig {
Expand All @@ -338,6 +354,8 @@ impl UdmaGlobalConfig for GlobalConfig {
) {
self.map_event(peripheral, event_type, to_channel);
}

fn reset(&self, peripheral: PeriphId) { self.reset(peripheral); }
}
// --------------------------------- DMA channel ------------------------------------
pub(crate) const CFG_EN: u32 = 0b01_0000; // start a transfer
Expand Down
1 change: 1 addition & 0 deletions loader/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ reset-debug = []
usb = []
qr = ["nalgebra"]
updates = []
cramium-mpw = ["cramium-hal/mpw"]

# general flags
debug-print = []
Expand Down
4 changes: 3 additions & 1 deletion loader/src/platform/cramium/cramium.rs
Original file line number Diff line number Diff line change
Expand Up @@ -300,7 +300,9 @@ pub fn early_init() -> u32 {
};

#[cfg(any(feature = "board-baosec", feature = "board-baosor"))]
let mut i2c = unsafe { cramium_hal::udma::I2c::new_with_ifram(i2c_channel, 400_000, perclk, i2c_ifram) };
let mut i2c = unsafe {
cramium_hal::udma::I2c::new_with_ifram(i2c_channel, 400_000, perclk, i2c_ifram, &udma_global)
};
// setup PMIC
#[cfg(any(feature = "board-baosec", feature = "board-baosor"))]
{
Expand Down
Loading

0 comments on commit bd3e99c

Please sign in to comment.