ast10x0: stabilize I3C IRQ exchange test
diff --git a/target/ast10x0/console_backend.rs b/target/ast10x0/console_backend.rs index 7f70753..e8fee17 100644 --- a/target/ast10x0/console_backend.rs +++ b/target/ast10x0/console_backend.rs
@@ -19,10 +19,14 @@ // Global console lock to serialize UART register access. static UART_LOCK: SpinLock<arch_arm_cortex_m::Arch, ()> = SpinLock::new(()); -#[unsafe(no_mangle)] -pub fn console_backend_write_all(buf: &[u8]) -> Result<()> { - let _lock = UART_LOCK.lock(arch_arm_cortex_m::Arch); +fn uart_write_all_unlocked(buf: &[u8]) -> Result<()> { // UART is configured by ROM/bootloader before firmware starts. let mut uart = unsafe { Usart::new_uninit(UART5_BASE) }; uart.write_all(buf).map_err(|_| Error::DataLoss) } + +#[unsafe(no_mangle)] +pub fn console_backend_write_all(buf: &[u8]) -> Result<()> { + let _lock = UART_LOCK.try_lock(arch_arm_cortex_m::Arch); + uart_write_all_unlocked(buf) +}
diff --git a/target/ast10x0/peripherals/i3c/ccc.rs b/target/ast10x0/peripherals/i3c/ccc.rs index 33190ba..8b6043b 100644 --- a/target/ast10x0/peripherals/i3c/ccc.rs +++ b/target/ast10x0/peripherals/i3c/ccc.rs
@@ -124,27 +124,15 @@ // ============================================================================= const fn ccc_enec(broadcast: bool) -> u8 { - if broadcast { - 0x00 - } else { - 0x80 - } + if broadcast { 0x00 } else { 0x80 } } const fn ccc_disec(broadcast: bool) -> u8 { - if broadcast { - 0x01 - } else { - 0x81 - } + if broadcast { 0x01 } else { 0x81 } } const fn ccc_rstact(broadcast: bool) -> u8 { - if broadcast { - 0x2a - } else { - 0x9a - } + if broadcast { 0x2a } else { 0x9a } } // =============================================================================
diff --git a/target/ast10x0/peripherals/i3c/config.rs b/target/ast10x0/peripherals/i3c/config.rs index c8ab963..2a52499 100644 --- a/target/ast10x0/peripherals/i3c/config.rs +++ b/target/ast10x0/peripherals/i3c/config.rs
@@ -40,10 +40,32 @@ /// Address allocation and tracking for I3C bus pub struct AddrBook { - /// Addresses currently in use - pub in_use: [bool; 128], - /// Reserved addresses (not available for allocation) - pub reserved: [bool; 128], + /// Bitmap (128 bits) of addresses currently in use. + in_use: [u32; 4], + /// Bitmap (128 bits) of reserved addresses (not available for allocation). + reserved: [u32; 4], +} + +impl AddrBook { + /// Read bit `addr` (0..=127) of a 128-bit map. The `& 3` index keeps this + /// panic-free (provably in `0..4`) for the `no_panics` analysis. + #[inline] + fn bit_get(bits: &[u32; 4], addr: u8) -> bool { + let i = addr as usize; + (bits[(i >> 5) & 3] >> (i & 31)) & 1 != 0 + } + + /// Set/clear bit `addr` (0..=127) of a 128-bit map (panic-free). + #[inline] + fn bit_set(bits: &mut [u32; 4], addr: u8, val: bool) { + let i = addr as usize; + let mask = 1u32 << (i & 31); + if val { + bits[(i >> 5) & 3] |= mask; + } else { + bits[(i >> 5) & 3] &= !mask; + } + } } impl Default for AddrBook { @@ -57,8 +79,8 @@ #[must_use] pub const fn new() -> Self { Self { - in_use: [false; 128], - reserved: [false; 128], + in_use: [0; 4], + reserved: [0; 4], } } @@ -66,7 +88,7 @@ #[inline] #[must_use] pub fn is_free(&self, addr: u8) -> bool { - !self.in_use[addr as usize] && !self.reserved[addr as usize] + !Self::bit_get(&self.in_use, addr) && !Self::bit_get(&self.reserved, addr) } /// Reserve default I3C addresses per specification @@ -75,16 +97,16 @@ /// differ from 0x7E by a single bit. pub fn reserve_defaults(&mut self) { // Reserve addresses 0-7 - for a in 0usize..=7 { - self.reserved[a] = true; + for a in 0u8..=7 { + Self::bit_set(&mut self.reserved, a, true); } // Reserve broadcast address - self.reserved[0x7E_usize] = true; + Self::bit_set(&mut self.reserved, 0x7E, true); // Reserve addresses differing from 0x7E by single bit for i in 0..=7 { let alt = 0x7E ^ (1u8 << i); if alt <= 0x7E { - self.reserved[alt as usize] = true; + Self::bit_set(&mut self.reserved, alt, true); } } } @@ -107,7 +129,7 @@ #[inline] pub fn mark_use(&mut self, addr: u8, used: bool) { if addr != 0 { - self.in_use[addr as usize] = used; + Self::bit_set(&mut self.in_use, addr, used); } } }
diff --git a/target/ast10x0/peripherals/i3c/hardware.rs b/target/ast10x0/peripherals/i3c/hardware.rs index 169dbe3..d2789fc 100644 --- a/target/ast10x0/peripherals/i3c/hardware.rs +++ b/target/ast10x0/peripherals/i3c/hardware.rs
@@ -28,29 +28,30 @@ use core::cell::RefCell; use critical_section::Mutex; -use super::ccc::{ccc_events_set, CccPayload}; -use super::config::{I3cConfig, I3C_MIN_CORE_CLK_SDR}; +use super::ccc::{CccPayload, ccc_events_set}; +use super::config::{I3C_MIN_CORE_CLK_SDR, I3cConfig}; use super::constants::{ - bit, field_get, field_prep, CM_TFR_STS_MASTER_HALT, CM_TFR_STS_TARGET_HALT, - COMMAND_ATTR_ADDR_ASSGN_CMD, COMMAND_ATTR_SLAVE_DATA_CMD, COMMAND_ATTR_XFER_ARG, - COMMAND_ATTR_XFER_CMD, COMMAND_PORT_ARG_DATA_LEN, COMMAND_PORT_ARG_DB, COMMAND_PORT_ATTR, - COMMAND_PORT_CMD, COMMAND_PORT_CP, COMMAND_PORT_DBP, COMMAND_PORT_DEV_COUNT, - COMMAND_PORT_DEV_INDEX, COMMAND_PORT_READ_TRANSFER, COMMAND_PORT_ROC, COMMAND_PORT_SPEED, - COMMAND_PORT_TID, COMMAND_PORT_TOC, DEV_ADDR_TABLE_IBI_MDB, DEV_ADDR_TABLE_IBI_PEC, - DEV_ADDR_TABLE_SIR_REJECT, I3CG_REG1_SCL_IN_SW_MODE_EN, I3CG_REG1_SCL_IN_SW_MODE_VAL, - I3CG_REG1_SDA_IN_SW_MODE_EN, I3CG_REG1_SDA_IN_SW_MODE_VAL, I3C_BCR_IBI_PAYLOAD_HAS_DATA_BYTE, - I3C_BUS_I2C_FMP_TF_MAX_NS, I3C_BUS_I2C_FMP_THIGH_MIN_NS, I3C_BUS_I2C_FMP_TLOW_MIN_NS, - I3C_BUS_I2C_FMP_TR_MAX_NS, I3C_BUS_I2C_FM_TF_MAX_NS, I3C_BUS_I2C_FM_THIGH_MIN_NS, - I3C_BUS_I2C_FM_TLOW_MIN_NS, I3C_BUS_I2C_FM_TR_MAX_NS, I3C_BUS_I2C_STD_TF_MAX_NS, - I3C_BUS_I2C_STD_THIGH_MIN_NS, I3C_BUS_I2C_STD_TLOW_MIN_NS, I3C_BUS_I2C_STD_TR_MAX_NS, - I3C_BUS_THIGH_MAX_NS, I3C_CCC_DEVCTRL, I3C_CCC_ENTDAA, I3C_CCC_EVT_INTR, I3C_CCC_SETHID, - I3C_MSG_READ, IBIQ_STATUS_IBI_DATA_LEN, IBIQ_STATUS_IBI_DATA_LEN_SHIFT, IBIQ_STATUS_IBI_ID, + CM_TFR_STS_MASTER_HALT, CM_TFR_STS_TARGET_HALT, COMMAND_ATTR_ADDR_ASSGN_CMD, + COMMAND_ATTR_SLAVE_DATA_CMD, COMMAND_ATTR_XFER_ARG, COMMAND_ATTR_XFER_CMD, + COMMAND_PORT_ARG_DATA_LEN, COMMAND_PORT_ARG_DB, COMMAND_PORT_ATTR, COMMAND_PORT_CMD, + COMMAND_PORT_CP, COMMAND_PORT_DBP, COMMAND_PORT_DEV_COUNT, COMMAND_PORT_DEV_INDEX, + COMMAND_PORT_READ_TRANSFER, COMMAND_PORT_ROC, COMMAND_PORT_SPEED, COMMAND_PORT_TID, + COMMAND_PORT_TOC, DEV_ADDR_TABLE_IBI_MDB, DEV_ADDR_TABLE_IBI_PEC, DEV_ADDR_TABLE_SIR_REJECT, + I3C_BCR_IBI_PAYLOAD_HAS_DATA_BYTE, I3C_BUS_I2C_FM_TF_MAX_NS, I3C_BUS_I2C_FM_THIGH_MIN_NS, + I3C_BUS_I2C_FM_TLOW_MIN_NS, I3C_BUS_I2C_FM_TR_MAX_NS, I3C_BUS_I2C_FMP_TF_MAX_NS, + I3C_BUS_I2C_FMP_THIGH_MIN_NS, I3C_BUS_I2C_FMP_TLOW_MIN_NS, I3C_BUS_I2C_FMP_TR_MAX_NS, + I3C_BUS_I2C_STD_TF_MAX_NS, I3C_BUS_I2C_STD_THIGH_MIN_NS, I3C_BUS_I2C_STD_TLOW_MIN_NS, + I3C_BUS_I2C_STD_TR_MAX_NS, I3C_BUS_THIGH_MAX_NS, I3C_CCC_DEVCTRL, I3C_CCC_ENTDAA, + I3C_CCC_EVT_INTR, I3C_CCC_SETHID, I3C_MSG_READ, I3CG_REG1_SCL_IN_SW_MODE_EN, + I3CG_REG1_SCL_IN_SW_MODE_VAL, I3CG_REG1_SDA_IN_SW_MODE_EN, I3CG_REG1_SDA_IN_SW_MODE_VAL, + IBIQ_STATUS_IBI_DATA_LEN, IBIQ_STATUS_IBI_DATA_LEN_SHIFT, IBIQ_STATUS_IBI_ID, IBIQ_STATUS_IBI_ID_SHIFT, INTR_CCC_UPDATED_STAT, INTR_DYN_ADDR_ASSGN_STAT, INTR_IBI_THLD_STAT, INTR_RESP_READY_STAT, INTR_TRANSFER_ABORT_STAT, INTR_TRANSFER_ERR_STAT, MAX_CMDS, NSEC_PER_SEC, RESET_CTRL_ALL, RESET_CTRL_QUEUES, RESET_CTRL_XFER_QUEUES, RESPONSE_ERROR_IBA_NACK, RESPONSE_PORT_DATA_LEN_MASK, RESPONSE_PORT_DATA_LEN_SHIFT, RESPONSE_PORT_ERR_STATUS_MASK, RESPONSE_PORT_ERR_STATUS_SHIFT, RESPONSE_PORT_TID_MASK, RESPONSE_PORT_TID_SHIFT, - SDA_TX_HOLD_MASK, SDA_TX_HOLD_MAX, SDA_TX_HOLD_MIN, SLV_DCR_MASK, SLV_EVENT_CTRL_SIR_EN, + SDA_TX_HOLD_MASK, SDA_TX_HOLD_MAX, SDA_TX_HOLD_MIN, SLV_DCR_MASK, SLV_EVENT_CTRL_SIR_EN, bit, + field_get, field_prep, }; use super::error::I3cError as I3cDrvError; use super::error::I3cError; @@ -1003,11 +1004,9 @@ } fn i3c_aspeed_isr(&mut self, config: &mut I3cConfig) { - self.disable_irq(); let status = self.i3c().i3cd03c().read().bits(); i3c_debug!(self.logger, "[ISR] 0x{:08x}", status); if status == 0 { - self.enable_irq(); return; } @@ -1040,7 +1039,6 @@ } self.i3c().i3cd03c().write(|w| unsafe { w.bits(status) }); - self.enable_irq(); } } @@ -1880,6 +1878,37 @@ let pos_opt = config.attached.pos_of_pid(pid); let pos: u8 = pos_opt.ok_or(I3cDrvError::NoDatPos)?; + if msgs.len() == 1 { + let mut cmd = I3cCmd::new(); + let cmds = core::slice::from_mut(&mut cmd); + + self.priv_xfer_build_cmds(cmds, msgs, pos)?; + + let mut xfer = I3cXfer::new(cmds); + self.start_xfer(config, &mut xfer); + + if !xfer.done.wait_for_us(1_000_000_000, &mut self.yield_fn) { + self.enter_halt(true, config); + self.reset_ctrl(RESET_CTRL_XFER_QUEUES); + self.exit_halt(config); + let _ = config + .curr_xfer + .swap(core::ptr::null_mut(), Ordering::AcqRel); + return Err(I3cDrvError::Timeout); + } + + if let Some(m) = msgs.first_mut() + && (m.flags & I3C_MSG_READ) != 0 + { + m.actual_len = xfer.cmds.first().map_or(0, |c| c.rx_len); + } + + return match xfer.ret { + 0 => Ok(()), + _ => Err(I3cDrvError::Timeout), + }; + } + let mut cmds: heapless::Vec<I3cCmd, MAX_CMDS> = heapless::Vec::new(); for _ in 0..msgs.len() { // `?` (not `.unwrap()`) keeps this panic-free; > MAX_CMDS msgs is a @@ -2058,11 +2087,21 @@ if rx_len != 0 { let mut buf: [u8; 256] = [0u8; 256]; - self.rd_rx_fifo(&mut buf[..rx_len]); + // Bound `rx_len` (a raw hardware field) to the buffer via + // `get_mut`: this ISR runs in handler mode, so an oversized + // length must not panic (same hardening as `end_xfer`). + let n = rx_len.min(buf.len()); + if let Some(dst) = buf.get_mut(..n) { + self.rd_rx_fifo(dst); + } + let _ = ibi_workq::i3c_ibi_work_enqueue_target_master_write( + I3C::BUS_NUM.into(), + buf.get(..n).unwrap_or(&[]), + ); i3c_debug!( self.logger, "[MASTER ==> TARGET] TARGET READ: {:02x?}", - &buf[..rx_len] + buf.get(..n).unwrap_or(&[]) ); }
diff --git a/target/ast10x0/peripherals/i3c/ibi.rs b/target/ast10x0/peripherals/i3c/ibi.rs index 1fdba45..3e590fd 100644 --- a/target/ast10x0/peripherals/i3c/ibi.rs +++ b/target/ast10x0/peripherals/i3c/ibi.rs
@@ -6,21 +6,27 @@ //! Handles IBI events including Hot-Join, SIR (Slave Interrupt Request), //! and target dynamic address assignment. //! -//! Ported from `aspeed-rust/src/i3c/ibi.rs` @ ce3b567. Two porting deltas: -//! - **D7 (heapless 0.9)**: `spsc::Producer`/`Consumer` lost their capacity -//! const-generic in 0.9 — they are now `Producer<'static, T>` / -//! `Consumer<'static, T>` (the reference used `<'static, T, N>` on 0.8). -//! - **edition 2024**: a direct reference to a `static mut` is denied -//! (`static_mut_refs`); the queue split goes through `addr_of_mut!` instead. +//! Ported from `aspeed-rust/src/i3c/ibi.rs` @ ce3b567. //! -//! The process-global queue/handler design itself is preserved (goal.md ADR-3): -//! an ISR cannot borrow a stack-owned device, so the IBI plane stays global, -//! arbitrated by `critical_section` + the SPSC discipline rather than by `&mut`. +//! **Porting delta (queue mechanism).** The reference uses `heapless::spsc` +//! `Producer`/`Consumer` handles, split once and parked in a global +//! `Mutex<RefCell<..>>`. On this target (heapless 0.9 + this toolchain) those +//! handles do not survive being stored in a `static` and re-accessed across +//! separate critical sections: a split that read back `prod=Some, cons=Some` +//! in-place would, after the consumer was taken in a later critical section, +//! read back `prod=None, cons=Some` — i.e. the niche-`Option`/`'static`-erased +//! handles got corrupted. The `RefCell` borrow flag was also observed stuck. +//! +//! So the SPSC split is replaced by a plain fixed-size ring buffer of +//! `Option<IbiWork>` (`IbiWork` is `Copy`, no niche pointers), guarded by the +//! same `critical_section`. The process-global queue/handler design (goal.md +//! ADR-3) is preserved — an ISR still cannot borrow a stack-owned device, so +//! the IBI plane stays global, arbitrated by `critical_section`. The public +//! API (`i3c_ibi_workq_consumer().dequeue()` + the three enqueue functions) is +//! unchanged. use core::cell::RefCell; -use core::ptr::addr_of_mut; use critical_section::Mutex; -use heapless::spsc::Queue; /// IBI queue depth const IBIQ_DEPTH: usize = 16; @@ -47,97 +53,121 @@ }, /// Target dynamic address assignment notification TargetDaAssignment, + /// Private write received by this target from the controller. + TargetMasterWrite { + /// Number of received bytes captured in `data`. + len: u8, + /// Received data, truncated to `IBI_DATA_MAX`. + data: [u8; IBI_DATA_MAX as usize], + }, } // ============================================================================= -// Static Queue Storage +// Static Ring-Buffer Storage // ============================================================================= -static mut IBIQ_BUFS: [Queue<IbiWork, IBIQ_DEPTH>; 4] = - [Queue::new(), Queue::new(), Queue::new(), Queue::new()]; - -struct IbiBus { - prod: Option<heapless::spsc::Producer<'static, IbiWork>>, - cons: Option<heapless::spsc::Consumer<'static, IbiWork>>, -} - -static IBI_WORKQS: [Mutex<RefCell<IbiBus>>; 4] = [ - Mutex::new(RefCell::new(IbiBus { - prod: None, - cons: None, - })), - Mutex::new(RefCell::new(IbiBus { - prod: None, - cons: None, - })), - Mutex::new(RefCell::new(IbiBus { - prod: None, - cons: None, - })), - Mutex::new(RefCell::new(IbiBus { - prod: None, - cons: None, - })), -]; - -// ============================================================================= -// Queue Management -// ============================================================================= - -/// Ensure the IBI queue for a bus has been split into producer/consumer. +/// Fixed-size single-producer/single-consumer ring of IBI work items. /// -/// Returns `false` if bus index is out of range. -fn ensure_ibiq_split(bus: usize) -> bool { - let Some(workq) = IBI_WORKQS.get(bus) else { - return false; - }; +/// All access is serialized by the per-bus `critical_section::Mutex`, so the +/// indices need no atomics; the producer is the I3C ISR and the consumer is the +/// owning test/driver loop. +struct IbiRing { + buf: [Option<IbiWork>; IBIQ_DEPTH], + head: usize, + len: usize, +} - critical_section::with(|cs| { - let Ok(mut b) = workq.borrow(cs).try_borrow_mut() else { - return; - }; - if b.prod.is_none() || b.cons.is_none() { - // SAFETY: `bus < 4` (checked by `IBI_WORKQS.get(bus)` above). Each - // bus's queue is split exactly once, inside this critical section, - // and `IBIQ_BUFS` is reached only here. Going through - // `addr_of_mut!` (not a direct `&mut IBIQ_BUFS`) satisfies the - // edition-2024 `static_mut_refs` rule; the Mutex + critical section - // serialize access so no aliasing `&mut` to the same element exists. - // `get_mut` (not `[bus]`) keeps the path panic-free for the - // `no_panics` analysis even though `bus` is in range. - let bufs: &'static mut [Queue<IbiWork, IBIQ_DEPTH>; 4] = - unsafe { &mut *addr_of_mut!(IBIQ_BUFS) }; - if let Some(queue) = bufs.get_mut(bus) { - let (p, c) = queue.split(); - b.prod = Some(p); - b.cons = Some(c); - } +impl IbiRing { + const fn new() -> Self { + Self { + buf: [None; IBIQ_DEPTH], + head: 0, + len: 0, } - }); - true -} - -/// Get the IBI work queue consumer for a bus -/// -/// Returns `None` if bus index is out of range or consumer already taken. -#[must_use] -pub fn i3c_ibi_workq_consumer(bus: usize) -> Option<heapless::spsc::Consumer<'static, IbiWork>> { - if !ensure_ibiq_split(bus) { - return None; } - let workq = IBI_WORKQS.get(bus)?; + fn push(&mut self, work: IbiWork) -> bool { + // `get_mut` + modulo keep this panic-free even if the indices were + // somehow out of range; `head` is normalized first. + self.head %= IBIQ_DEPTH; + if self.len >= IBIQ_DEPTH { + return false; + } + let idx = (self.head + self.len) % IBIQ_DEPTH; + if let Some(slot) = self.buf.get_mut(idx) { + *slot = Some(work); + self.len += 1; + true + } else { + false + } + } - // `try_borrow_mut` (not `borrow_mut`) keeps the path panic-free for the - // `no_panics` analysis. Inside this critical section a conflicting borrow - // is impossible, so the `Err` arm is unreachable in practice. - critical_section::with(|cs| { - workq - .borrow(cs) - .try_borrow_mut() - .ok() - .and_then(|mut b| b.cons.take()) - }) + fn pop(&mut self) -> Option<IbiWork> { + self.head %= IBIQ_DEPTH; + if self.len == 0 || self.len > IBIQ_DEPTH { + // Empty, or a corrupt length — treat as empty (panic-free). + return None; + } + let work = self.buf.get_mut(self.head).and_then(Option::take); + self.head = (self.head + 1) % IBIQ_DEPTH; + self.len -= 1; + work + } +} + +static IBI_RINGS: [Mutex<RefCell<IbiRing>>; 4] = [ + Mutex::new(RefCell::new(IbiRing::new())), + Mutex::new(RefCell::new(IbiRing::new())), + Mutex::new(RefCell::new(IbiRing::new())), + Mutex::new(RefCell::new(IbiRing::new())), +]; + +/// Run `f` against the ring for `bus`, serialized by the critical section. +/// +/// Returns `None` if `bus` is out of range. +fn with_ring<R>(bus: usize, f: impl FnOnce(&mut IbiRing) -> R) -> Option<R> { + let workq = IBI_RINGS.get(bus)?; + Some(critical_section::with(|cs| { + // SAFETY: the critical section serializes all access to this ring, so no + // other reference is live. We go through `as_ptr()` rather than + // `borrow_mut()`/`try_borrow_mut()` because the `RefCell` runtime borrow + // flag is unreliable on this target (it stuck "borrowed" after a clean + // borrow/release); mutual exclusion comes from the critical section. + let ring: &mut IbiRing = unsafe { &mut *workq.borrow(cs).as_ptr() }; + f(ring) + })) +} + +// ============================================================================= +// Consumer Handle +// ============================================================================= + +/// Consumer handle for a bus's IBI work queue. +/// +/// Holds no state beyond the bus index; dequeuing reads the shared ring under +/// the critical section. Returned by [`i3c_ibi_workq_consumer`]. +pub struct IbiConsumer { + bus: usize, +} + +impl IbiConsumer { + /// Dequeue the next IBI work item, if any. + #[must_use] + pub fn dequeue(&mut self) -> Option<IbiWork> { + with_ring(self.bus, IbiRing::pop).flatten() + } +} + +/// Get the IBI work queue consumer for a bus. +/// +/// Returns `None` if the bus index is out of range. +#[must_use] +pub fn i3c_ibi_workq_consumer(bus: usize) -> Option<IbiConsumer> { + if bus >= IBI_RINGS.len() { + return None; + } + Some(IbiConsumer { bus }) } // ============================================================================= @@ -147,59 +177,38 @@ /// Enqueue a target dynamic address assignment notification #[must_use] pub fn i3c_ibi_work_enqueue_target_da_assignment(bus: usize) -> bool { - if !ensure_ibiq_split(bus) { - return false; - } - critical_section::with(|cs| { - if let Some(workq) = IBI_WORKQS.get(bus) { - let mut ibi_bus = workq.borrow(cs).borrow_mut(); - if let Some(prod) = ibi_bus.prod.as_mut() { - return prod.enqueue(IbiWork::TargetDaAssignment).is_ok(); - } - } - false - }) + with_ring(bus, |r| r.push(IbiWork::TargetDaAssignment)).unwrap_or(false) } /// Enqueue a Hot-Join notification #[must_use] pub fn i3c_ibi_work_enqueue_hotjoin(bus: usize) -> bool { - if !ensure_ibiq_split(bus) { - return false; - } - critical_section::with(|cs| { - if let Some(workq) = IBI_WORKQS.get(bus) { - let mut ibi_bus = workq.borrow(cs).borrow_mut(); - if let Some(prod) = ibi_bus.prod.as_mut() { - return prod.enqueue(IbiWork::HotJoin).is_ok(); - } - } - false - }) + with_ring(bus, |r| r.push(IbiWork::HotJoin)).unwrap_or(false) } /// Enqueue a target interrupt (SIR) notification #[must_use] pub fn i3c_ibi_work_enqueue_target_irq(bus: usize, addr: u8, data: &[u8]) -> bool { - if !ensure_ibiq_split(bus) { - return false; - } let mut ibi_buf = [0u8; IBI_DATA_MAX as usize]; let take = core::cmp::min(IBI_DATA_MAX as usize, data.len()); ibi_buf[..take].copy_from_slice(&data[..take]); - critical_section::with(|cs| { - if let Some(workq) = IBI_WORKQS.get(bus) { - let mut i3c_bus = workq.borrow(cs).borrow_mut(); - if let Some(prod) = i3c_bus.prod.as_mut() { - return prod - .enqueue(IbiWork::Sirq { - addr, - len: u8::try_from(take).unwrap_or(IBI_DATA_MAX), - data: ibi_buf, - }) - .is_ok(); - } - } - false - }) + let work = IbiWork::Sirq { + addr, + len: u8::try_from(take).unwrap_or(IBI_DATA_MAX), + data: ibi_buf, + }; + with_ring(bus, |r| r.push(work)).unwrap_or(false) +} + +/// Enqueue a private write received by this target from the controller. +#[must_use] +pub fn i3c_ibi_work_enqueue_target_master_write(bus: usize, data: &[u8]) -> bool { + let mut buf = [0u8; IBI_DATA_MAX as usize]; + let take = core::cmp::min(IBI_DATA_MAX as usize, data.len()); + buf[..take].copy_from_slice(&data[..take]); + let work = IbiWork::TargetMasterWrite { + len: u8::try_from(take).unwrap_or(IBI_DATA_MAX), + data: buf, + }; + with_ring(bus, |r| r.push(work)).unwrap_or(false) }
diff --git a/target/ast10x0/peripherals/i3c/mod.rs b/target/ast10x0/peripherals/i3c/mod.rs index 809b4a4..2ebe6de 100644 --- a/target/ast10x0/peripherals/i3c/mod.rs +++ b/target/ast10x0/peripherals/i3c/mod.rs
@@ -54,8 +54,8 @@ // Configuration pub use config::{ - AddrBook, Attached, CommonCfg, CommonState, DeviceEntry, I3cConfig, I3cTargetConfig, ResetSpec, - I3C_MAX_CORE_CLK, I3C_MIN_CORE_CLK_HDR, I3C_MIN_CORE_CLK_SDR, + AddrBook, Attached, CommonCfg, CommonState, DeviceEntry, I3C_MAX_CORE_CLK, + I3C_MIN_CORE_CLK_HDR, I3C_MIN_CORE_CLK_SDR, I3cConfig, I3cTargetConfig, ResetSpec, }; // Core types @@ -66,21 +66,22 @@ // Hardware interface pub use hardware::{ - dispatch_i3c_irq, register_i3c_irq_handler, Ast1060I3c, HardwareClock, HardwareCore, - HardwareFifo, HardwareInterface, HardwareRecovery, HardwareTarget, HardwareTransfer, Instance, + Ast1060I3c, HardwareClock, HardwareCore, HardwareFifo, HardwareInterface, HardwareRecovery, + HardwareTarget, HardwareTransfer, Instance, dispatch_i3c_irq, register_i3c_irq_handler, }; // CCC operations pub use ccc::{ - ccc_events_all_set, ccc_events_set, ccc_getbcr, ccc_getpid, ccc_getstatus, ccc_getstatus_fmt1, - ccc_rstact_all, ccc_rstdaa_all, ccc_setnewda, Ccc, CccPayload, CccRstActDefByte, - CccTargetPayload, GetStatusDefByte, GetStatusFormat, GetStatusResp, + Ccc, CccPayload, CccRstActDefByte, CccTargetPayload, GetStatusDefByte, GetStatusFormat, + GetStatusResp, ccc_events_all_set, ccc_events_set, ccc_getbcr, ccc_getpid, ccc_getstatus, + ccc_getstatus_fmt1, ccc_rstact_all, ccc_rstdaa_all, ccc_setnewda, }; // IBI work queue pub use ibi::{ - i3c_ibi_work_enqueue_hotjoin, i3c_ibi_work_enqueue_target_da_assignment, - i3c_ibi_work_enqueue_target_irq, i3c_ibi_workq_consumer, IbiWork, + IbiConsumer, IbiWork, i3c_ibi_work_enqueue_hotjoin, i3c_ibi_work_enqueue_target_da_assignment, + i3c_ibi_work_enqueue_target_irq, i3c_ibi_work_enqueue_target_master_write, + i3c_ibi_workq_consumer, }; // Constants (wildcard export for convenience)
diff --git a/target/ast10x0/tests/peripherals/i3c/i3c_irq/BUILD.bazel b/target/ast10x0/tests/peripherals/i3c/i3c_irq/BUILD.bazel index dd5f0bf..8428ddc 100644 --- a/target/ast10x0/tests/peripherals/i3c/i3c_irq/BUILD.bazel +++ b/target/ast10x0/tests/peripherals/i3c/i3c_irq/BUILD.bazel
@@ -97,6 +97,7 @@ target_codegen( name = "slave_codegen", arch = "@pigweed//pw_kernel/arch/arm_cortex_m:arch_arm_cortex_m", + crate_name = "codegen", system_config = ":slave_system_config", target_compatible_with = TARGET_COMPATIBLE_WITH, ) @@ -112,7 +113,6 @@ rust_binary( name = "slave_target", srcs = ["slave_target.rs"], - aliases = {":slave_codegen": "codegen"}, edition = "2024", tags = ["kernel"], target_compatible_with = TARGET_COMPATIBLE_WITH,
diff --git a/target/ast10x0/tests/peripherals/i3c/i3c_irq/slave_system.json5 b/target/ast10x0/tests/peripherals/i3c/i3c_irq/slave_system.json5 index 77d3701..dd904d4 100644 --- a/target/ast10x0/tests/peripherals/i3c/i3c_irq/slave_system.json5 +++ b/target/ast10x0/tests/peripherals/i3c/i3c_irq/slave_system.json5
@@ -6,12 +6,18 @@ arch: { type: "armv7m", vector_table_start_address: 0x00000000, - vector_table_size_bytes: 1280, + vector_table_size_bytes: 1536, // 0x600: exception vectors + PW_KERNEL_INTERRUPT_TABLE_ARRAY up to IRQ 104 }, kernel: { - flash_start_address: 0x00000500, + flash_start_address: 0x00000600, flash_size_bytes: 262144, - ram_start_address: 0x00040500, + ram_start_address: 0x00040600, ram_size_bytes: 393216, + interrupt_table: { + table: { + // I3C2 IRQ (bus 2, used by the HV IBI test). + "104": "crate::i3c2_irq", + }, + }, }, }
diff --git a/target/ast10x0/tests/peripherals/i3c/i3c_irq/slave_target.rs b/target/ast10x0/tests/peripherals/i3c/i3c_irq/slave_target.rs index 732fcef..f5a562e 100644 --- a/target/ast10x0/tests/peripherals/i3c/i3c_irq/slave_target.rs +++ b/target/ast10x0/tests/peripherals/i3c/i3c_irq/slave_target.rs
@@ -24,29 +24,117 @@ use ast10x0_board::{Ast10x0Board, Ast10x0BoardDescriptor}; use ast10x0_peripherals::i3c::{ - i3c_ibi_workq_consumer, Ast1060I3c, HardwareCore, HardwareTarget, HardwareTransfer, I3cConfig, - I3cController, I3cTargetConfig, IbiWork, + Ast1060I3c, HardwareCore, HardwareTarget, HardwareTransfer, I3cConfig, I3cController, + I3cTargetConfig, IbiConsumer, IbiWork, i3c_ibi_workq_consumer, }; use ast10x0_peripherals::scu::pinctrl; use codegen as _; use console_backend::console_backend_write_all; use entry as _; -use target_common::{declare_target, TargetInterface}; +use kernel::Kernel; +use target_common::{TargetInterface, declare_target}; pub struct Target {} /// Number of IBIs the target raises once it has a dynamic address. const MAX_IBIS: u32 = 10; +/// Give the controller time to finish init and open the hot-join ACK window. +const HOT_JOIN_STARTUP_DELAY_SPINS: u32 = 0x1000_0000; +/// Re-raise hot-join while waiting in case the first request hit the NACK window. +const HOT_JOIN_RETRY_SPINS: u32 = 0x0400_0000; +const WAIT_MASTER_WRITE_SPINS: u32 = 0x0400_0000; +const XFER_DATA_LEN: usize = 16; -fn run_target() -> Result<(), &'static str> { - pw_log::info!("####### I3C target test #######"); +fn spin_wait(mut cycles: u32) { + while cycles != 0 { + core::hint::spin_loop(); + cycles = cycles.wrapping_sub(1); + } +} - let board = Ast10x0Board::new(Ast10x0BoardDescriptor { - pinctrl_groups: &[pinctrl::PINCTRL_HVI3C2], - }); - // SAFETY: single call at boot with exclusive access to the board. - unsafe { board.init() }; +fn log_target_hj_state(label: u32) { + let regs = unsafe { &*ast1060_pac::I3c2::ptr() }; + let dev_addr = regs.i3cd004().read().bits(); + let event_ctrl = regs.i3cd038().read().bits(); + let device_char = regs.i3cd008().read().bits(); + pw_log::info!( + "[DBG] target hj label={} dev_addr={}", + label as u32, + dev_addr as u32 + ); + pw_log::info!( + "[DBG] target hj event_ctrl={} device_char={}", + event_ctrl as u32, + device_char as u32 + ); +} +fn log_target_master_write(len: u8, data: &[u8; XFER_DATA_LEN]) { + pw_log::info!( + "[MASTER ==> TARGET] target received {} bytes: {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x}", + len as u32, + data[0] as u32, + data[1] as u32, + data[2] as u32, + data[3] as u32, + data[4] as u32, + data[5] as u32, + data[6] as u32, + data[7] as u32 + ); + pw_log::info!( + "[MASTER ==> TARGET] target received cont {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x}", + data[8] as u32, + data[9] as u32, + data[10] as u32, + data[11] as u32, + data[12] as u32, + data[13] as u32, + data[14] as u32, + data[15] as u32 + ); +} + +fn wait_for_master_write(ibi_cons: &mut IbiConsumer) -> Result<(), &'static str> { + let mut spin_count = 0u32; + loop { + let Some(work) = ibi_cons.dequeue() else { + core::hint::spin_loop(); + spin_count = spin_count.wrapping_add(1); + if spin_count >= WAIT_MASTER_WRITE_SPINS { + return Err("master write not received"); + } + continue; + }; + + match work { + IbiWork::TargetMasterWrite { len, data } => { + log_target_master_write(len, &data); + return Ok(()); + } + IbiWork::TargetDaAssignment => pw_log::info!("[IBI] TargetDaAssignment"), + IbiWork::HotJoin => pw_log::info!("[IBI] hotjoin"), + IbiWork::Sirq { addr, len, .. } => { + pw_log::info!("[IBI] SIRQ from 0x{:02x} len {}", addr as u32, len as u32); + } + } + } +} + +/// Calibrated busy-wait used as the driver's yield/delay hook. Mirrors the +/// reference `DummyDelay::delay_ns` (busy-loop of ~`ns / 100` nops). A named +/// `fn` (not a closure) keeps [`build_target`]'s return type nameable. +fn yield_delay(ns: u32) { + for _ in 0..(ns / 100) { + core::hint::spin_loop(); + } +} + +/// Build + validate the target controller in its own `#[inline(never)]` frame +/// so the temporary `I3cConfig` (256-byte `AddrBook` inside) is freed on return +/// rather than lingering alongside `ctrl` on the 2 KiB kernel bootstrap stack. +#[inline(never)] +fn build_target() -> Result<I3cController<Ast1060I3c<ast1060_pac::I3c2, fn(u32)>>, &'static str> { // Secondary (target) timing — identical to the reference target. let mut config = I3cConfig::new() .core_clk_hz(200_000_000) @@ -66,12 +154,29 @@ .map_err(|_| "invalid clock configuration")?; // SAFETY: the test owns I3C bus 2 and uses the matching PAC blocks. - let hw = unsafe { Ast1060I3c::<ast1060_pac::I3c2, _>::new(|_| core::hint::spin_loop()) }; - let mut ctrl = I3cController::new(hw, config); - ctrl.init_hardware(); + let hw = unsafe { Ast1060I3c::<ast1060_pac::I3c2, fn(u32)>::new(yield_delay) }; + Ok(I3cController::new(hw, config)) +} +fn run_target() -> Result<(), &'static str> { + pw_log::info!("####### I3C target test #######"); + + let board = Ast10x0Board::new(Ast10x0BoardDescriptor { + pinctrl_groups: &[pinctrl::PINCTRL_HVI3C2], + }); + // SAFETY: single call at boot with exclusive access to the board. + unsafe { board.init() }; + + // Build the controller in a separate (never-inlined) frame so the temporary + // `I3cConfig` (256-byte `AddrBook` inside) is freed before `ctrl` is used — + // the kernel bootstrap thread stack is only 2 KiB and two live `I3cConfig`s + // overflow it. See `build_target`. + let mut ctrl = build_target()?; let bus = ctrl.hw.bus_num() as usize; let mut ibi_cons = i3c_ibi_workq_consumer(bus).ok_or("IBI consumer unavailable")?; + pw_log::info!("IBI work queue ready on bus {}", bus as u32); + + ctrl.init_hardware(); let dyn_addr = 8u8; let dev_idx = 0usize; @@ -82,13 +187,25 @@ dyn_addr as u32 ); + pw_log::info!("waiting before hot-join..."); + spin_wait(HOT_JOIN_STARTUP_DELAY_SPINS); pw_log::info!("raising hot-join; waiting for dynamic address assignment..."); - let _ = ctrl.hw.target_ibi_raise_hj(&mut ctrl.config); + let hj_ok = ctrl.hw.target_ibi_raise_hj(&mut ctrl.config).is_ok(); + pw_log::info!("[DBG] hot-join raise ok={}", hj_ok as u32); + log_target_hj_state(0); // Wait for the controller to assign our dynamic address. + let mut spin_count = 0u32; loop { let Some(work) = ibi_cons.dequeue() else { core::hint::spin_loop(); + spin_count = spin_count.wrapping_add(1); + if spin_count & (HOT_JOIN_RETRY_SPINS - 1) == 0 { + pw_log::info!("[DBG] retry hot-join"); + let hj_ok = ctrl.hw.target_ibi_raise_hj(&mut ctrl.config).is_ok(); + pw_log::info!("[DBG] hot-join retry ok={}", hj_ok as u32); + log_target_hj_state(1); + } continue; }; match work { @@ -104,6 +221,9 @@ IbiWork::Sirq { addr, len, .. } => { pw_log::info!("[IBI] SIRQ from 0x{:02x} len {}", addr as u32, len as u32); } + IbiWork::TargetMasterWrite { len, data } => { + log_target_master_write(len, &data); + } } } @@ -121,6 +241,7 @@ if ctrl.target_get_ibi_payload(&mut data).is_err() { return Err("target_get_ibi_payload failed"); } + wait_for_master_write(&mut ibi_cons)?; ibi_count += 1; } @@ -128,6 +249,12 @@ Ok(()) } +pub fn i3c2_irq<K: Kernel>(_k: K) { + ast10x0_peripherals::i3c::dispatch_i3c_irq(2); +} + +codegen::declare_kernel_interrupt_handlers!(); + impl TargetInterface for Target { const NAME: &'static str = "AST10x0 Kernel I3C IBI (target)";
diff --git a/target/ast10x0/tests/peripherals/i3c/i3c_irq/system.json5 b/target/ast10x0/tests/peripherals/i3c/i3c_irq/system.json5 index 3ecb32c..e9d3c1c 100644 --- a/target/ast10x0/tests/peripherals/i3c/i3c_irq/system.json5 +++ b/target/ast10x0/tests/peripherals/i3c/i3c_irq/system.json5
@@ -7,12 +7,18 @@ arch: { type: "armv7m", vector_table_start_address: 0x00000000, - vector_table_size_bytes: 1280, // 0x500 (320 vectors) + vector_table_size_bytes: 1536, // 0x600: exception vectors + PW_KERNEL_INTERRUPT_TABLE_ARRAY up to IRQ 104 }, kernel: { - flash_start_address: 0x00000500, // After vector table + flash_start_address: 0x00000600, // After vector table flash_size_bytes: 262144, // 256KB for kernel code (in RAM) - ram_start_address: 0x00040500, // RAM starts after code + ram_start_address: 0x00040600, // RAM starts after code ram_size_bytes: 393216, // 384KB for data + interrupt_table: { + table: { + // I3C2 IRQ (bus 2, used by the HV IBI test). + "104": "crate::i3c2_irq", + }, + }, }, }
diff --git a/target/ast10x0/tests/peripherals/i3c/i3c_irq/target.rs b/target/ast10x0/tests/peripherals/i3c/i3c_irq/target.rs index dfa801d..c0d8c38 100644 --- a/target/ast10x0/tests/peripherals/i3c/i3c_irq/target.rs +++ b/target/ast10x0/tests/peripherals/i3c/i3c_irq/target.rs
@@ -28,31 +28,96 @@ use ast10x0_board::{Ast10x0Board, Ast10x0BoardDescriptor}; use ast10x0_peripherals::i3c::{ - i3c_ibi_workq_consumer, Ast1060I3c, HardwareCore, HardwareTransfer, I3cConfig, I3cController, - I3cMsg, IbiWork, I3C_MSG_READ, I3C_MSG_STOP, I3C_MSG_WRITE, + Ast1060I3c, HardwareCore, HardwareTransfer, I3C_MSG_READ, I3C_MSG_STOP, I3C_MSG_WRITE, + I3cConfig, I3cController, I3cMsg, IbiWork, i3c_ibi_workq_consumer, }; use ast10x0_peripherals::scu::pinctrl; use codegen as _; use console_backend::console_backend_write_all; use entry as _; -use target_common::{declare_target, TargetInterface}; +use kernel::Kernel; +use target_common::{TargetInterface, declare_target}; pub struct Target {} +type I3c2Hw = Ast1060I3c<ast1060_pac::I3c2, fn(u32)>; +type I3c2Controller = I3cController<I3c2Hw>; + /// PID of the peer target (matches the `:slave` image / the reference). const KNOWN_PID: u64 = 0x07ec_a003_2000; /// Stop after this many master<->target exchanges. const MAX_EXCHANGES: u32 = 10; +const XFER_DATA_LEN: usize = 16; +const WAIT_LOG_SPINS: u32 = 0x0400_0000; -fn run_controller() -> Result<(), &'static str> { - pw_log::info!("####### I3C master test #######"); +/// Calibrated busy-wait used as the driver's yield/delay hook. Mirrors the +/// reference `DummyDelay::delay_ns` (busy-loop of ~`ns / 100` nops). A named +/// `fn` (not a closure) keeps [`build_controller`]'s return type nameable. +fn yield_delay(ns: u32) { + for _ in 0..(ns / 100) { + core::hint::spin_loop(); + } +} - let board = Ast10x0Board::new(Ast10x0BoardDescriptor { - pinctrl_groups: &[pinctrl::PINCTRL_HVI3C2], - }); - // SAFETY: single call at boot with exclusive access to the board. - unsafe { board.init() }; +fn log_master_read_payload(len: u32, data: &[u8; XFER_DATA_LEN]) { + pw_log::info!( + "[MASTER <== TARGET] data len={} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x}", + len as u32, + data[0] as u32, + data[1] as u32, + data[2] as u32, + data[3] as u32, + data[4] as u32, + data[5] as u32, + data[6] as u32, + data[7] as u32 + ); + pw_log::info!( + "[MASTER <== TARGET] data cont {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x}", + data[8] as u32, + data[9] as u32, + data[10] as u32, + data[11] as u32, + data[12] as u32, + data[13] as u32, + data[14] as u32, + data[15] as u32 + ); +} +fn log_master_write_payload(data: &[u8; XFER_DATA_LEN]) { + pw_log::info!( + "[MASTER ==> TARGET] data {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x}", + data[0] as u32, + data[1] as u32, + data[2] as u32, + data[3] as u32, + data[4] as u32, + data[5] as u32, + data[6] as u32, + data[7] as u32 + ); + pw_log::info!( + "[MASTER ==> TARGET] data cont {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x}", + data[8] as u32, + data[9] as u32, + data[10] as u32, + data[11] as u32, + data[12] as u32, + data[13] as u32, + data[14] as u32, + data[15] as u32 + ); +} + +/// Build + validate the controller in its own `#[inline(never)]` frame. +/// +/// The temporary `I3cConfig` embeds a 256-byte `AddrBook`; keeping it live +/// alongside `ctrl` (which owns a moved copy) would put two `I3cConfig`s on the +/// 2 KiB kernel bootstrap stack and overflow it. Building here frees the +/// temporary on return, leaving `run_controller` with only `ctrl`. +#[inline(never)] +fn build_controller() -> Result<I3c2Controller, &'static str> { // Controller (primary) timing — identical to the reference master. let mut config = I3cConfig::new() .core_clk_hz(200_000_000) @@ -69,14 +134,78 @@ .validate_clock() .map_err(|_| "invalid clock configuration")?; - // SAFETY: the test owns I3C bus 2 and uses the matching PAC blocks; the - // busy-spin closure is the bare-metal wait policy. - let hw = unsafe { Ast1060I3c::<ast1060_pac::I3c2, _>::new(|_| core::hint::spin_loop()) }; - let mut ctrl = I3cController::new(hw, config); - ctrl.init_hardware(); + // SAFETY: the test owns I3C bus 2 and uses the matching PAC blocks. + let hw = unsafe { Ast1060I3c::<ast1060_pac::I3c2, fn(u32)>::new(yield_delay) }; + Ok(I3cController::new(hw, config)) +} +#[inline(never)] +fn master_read_from_target( + ctrl: &mut I3c2Controller, +) -> Result<(u32, [u8; XFER_DATA_LEN]), &'static str> { + let mut rx_buf = [0u8; 128]; + let actual_len = { + let mut rd_msgs = [I3cMsg { + buf: Some(&mut rx_buf[..]), + actual_len: 128, + num_xfer: 0, + flags: I3C_MSG_READ | I3C_MSG_STOP, + hdr_mode: 0, + hdr_cmd_mode: 0, + }]; + ctrl.hw + .priv_xfer(&mut ctrl.config, KNOWN_PID, &mut rd_msgs) + .map_err(|_| "private read failed")?; + rd_msgs[0].actual_len + }; + let mut data = [0u8; XFER_DATA_LEN]; + let take = (actual_len as usize).min(data.len()).min(rx_buf.len()); + data[..take].copy_from_slice(&rx_buf[..take]); + Ok((actual_len, data)) +} + +#[inline(never)] +fn master_write_to_target(ctrl: &mut I3c2Controller) -> Result<(), &'static str> { + let mut tx_buf: [u8; XFER_DATA_LEN] = [ + 0xde, 0xad, 0xbe, 0xef, 0xca, 0xfe, 0xba, 0xbe, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, + 0x88, + ]; + let mut wr_msgs = [I3cMsg { + buf: Some(&mut tx_buf[..]), + actual_len: 16, + num_xfer: 0, + flags: I3C_MSG_WRITE | I3C_MSG_STOP, + hdr_mode: 0, + hdr_cmd_mode: 0, + }]; + ctrl.hw + .priv_xfer(&mut ctrl.config, KNOWN_PID, &mut wr_msgs) + .map_err(|_| "private write failed")?; + log_master_write_payload(&tx_buf); + Ok(()) +} + +fn run_controller() -> Result<(), &'static str> { + pw_log::info!("####### I3C master test #######"); + + let board = Ast10x0Board::new(Ast10x0BoardDescriptor { + pinctrl_groups: &[pinctrl::PINCTRL_HVI3C2], + }); + // SAFETY: single call at boot with exclusive access to the board. + unsafe { board.init() }; + + // Build the controller in a separate (never-inlined) frame so the temporary + // `I3cConfig` is freed before the long-lived `ctrl` is used (see + // `build_controller`): the kernel bootstrap thread stack is only 2 KiB and + // two live `I3cConfig`s (each embeds a 256-byte `AddrBook`) overflow it. + let mut ctrl = build_controller()?; let bus = ctrl.hw.bus_num() as usize; let mut ibi_cons = i3c_ibi_workq_consumer(bus).ok_or("IBI consumer unavailable")?; + pw_log::info!("IBI work queue ready on bus {}", bus as u32); + + pw_log::info!("initializing I3C2 controller"); + ctrl.init_hardware(); + pw_log::info!("I3C2 controller ready"); let dyn_addr = ctrl .config @@ -92,9 +221,47 @@ pw_log::info!("pre-attached dev at slot 0, dyn addr {}", dyn_addr as u32); let mut received = 0u32; + let mut spin_count = 0u32; loop { let Some(work) = ibi_cons.dequeue() else { core::hint::spin_loop(); + spin_count = spin_count.wrapping_add(1); + if spin_count & (WAIT_LOG_SPINS - 1) == 0 { + let irq_count = I3C2_IRQ_COUNT.load(core::sync::atomic::Ordering::Relaxed); + let status = I3C2_LAST_STATUS.load(core::sync::atomic::Ordering::Relaxed); + let queue_status = + I3C2_LAST_QUEUE_STATUS.load(core::sync::atomic::Ordering::Relaxed); + let status_en = I3C2_LAST_STATUS_EN.load(core::sync::atomic::Ordering::Relaxed); + let signal_en = I3C2_LAST_SIGNAL_EN.load(core::sync::atomic::Ordering::Relaxed); + let ibi_count = (queue_status >> 24) & 0x1f; + let ibi_buf_blr = (queue_status >> 16) & 0xff; + let resp_blr = (queue_status >> 8) & 0xff; + pw_log::info!( + "[DBG] waiting irq_count={} spin={}", + irq_count as u32, + spin_count as u32 + ); + pw_log::info!( + "[DBG] i3c2 status={} queue={}", + status as u32, + queue_status as u32 + ); + pw_log::info!( + "[DBG] i3c2 ibi_count={} ibi_buf_blr={}", + ibi_count as u32, + ibi_buf_blr as u32 + ); + pw_log::info!( + "[DBG] i3c2 resp_blr={} status_en={}", + resp_blr as u32, + status_en as u32 + ); + pw_log::info!( + "[DBG] i3c2 signal_en={} reserved={}", + signal_en as u32, + 0 as u32 + ); + } continue; }; match work { @@ -109,49 +276,59 @@ pw_log::error!("acknowledge_ibi failed"); } - // Private read: MASTER <== TARGET - let mut rx_buf = [0u8; 128]; - let mut rd_msgs = [I3cMsg { - buf: Some(&mut rx_buf[..]), - actual_len: 128, - num_xfer: 0, - flags: I3C_MSG_READ | I3C_MSG_STOP, - hdr_mode: 0, - hdr_cmd_mode: 0, - }]; - let _ = ctrl.hw.priv_xfer(&mut ctrl.config, KNOWN_PID, &mut rd_msgs); - pw_log::info!( - "[MASTER <== TARGET] read {} bytes", - rd_msgs[0].actual_len as u32 - ); + let (read_len, read_data) = master_read_from_target(&mut ctrl)?; + pw_log::info!("[MASTER <== TARGET] read {} bytes", read_len as u32); + log_master_read_payload(read_len, &read_data); received += 1; - if received > MAX_EXCHANGES { + master_write_to_target(&mut ctrl)?; + pw_log::info!("[MASTER ==> TARGET] wrote 16 bytes"); + + if received >= MAX_EXCHANGES { pw_log::info!("I3C master test done"); return Ok(()); } - - // Private write: MASTER ==> TARGET - let mut tx_buf: [u8; 16] = [ - 0xde, 0xad, 0xbe, 0xef, 0xca, 0xfe, 0xba, 0xbe, 0x11, 0x22, 0x33, 0x44, 0x55, - 0x66, 0x77, 0x88, - ]; - let mut wr_msgs = [I3cMsg { - buf: Some(&mut tx_buf[..]), - actual_len: 16, - num_xfer: 0, - flags: I3C_MSG_WRITE | I3C_MSG_STOP, - hdr_mode: 0, - hdr_cmd_mode: 0, - }]; - let _ = ctrl.hw.priv_xfer(&mut ctrl.config, KNOWN_PID, &mut wr_msgs); - pw_log::info!("[MASTER ==> TARGET] wrote 16 bytes"); } IbiWork::TargetDaAssignment => pw_log::info!("[IBI] TargetDaAssignment"), + IbiWork::TargetMasterWrite { len, .. } => { + pw_log::info!("[IBI] TargetMasterWrite len {}", len as u32); + } } } } +static I3C2_IRQ_COUNT: core::sync::atomic::AtomicU32 = core::sync::atomic::AtomicU32::new(0); +static I3C2_LAST_STATUS: core::sync::atomic::AtomicU32 = core::sync::atomic::AtomicU32::new(0); +static I3C2_LAST_QUEUE_STATUS: core::sync::atomic::AtomicU32 = + core::sync::atomic::AtomicU32::new(0); +static I3C2_LAST_STATUS_EN: core::sync::atomic::AtomicU32 = core::sync::atomic::AtomicU32::new(0); +static I3C2_LAST_SIGNAL_EN: core::sync::atomic::AtomicU32 = core::sync::atomic::AtomicU32::new(0); + +pub fn i3c2_irq<K: Kernel>(_k: K) { + // Do not read i3cd018 here: that register pops the IBI queue entry. + let regs = unsafe { &*ast1060_pac::I3c2::ptr() }; + I3C2_LAST_STATUS.store( + regs.i3cd03c().read().bits(), + core::sync::atomic::Ordering::Relaxed, + ); + I3C2_LAST_QUEUE_STATUS.store( + regs.i3cd04c().read().bits(), + core::sync::atomic::Ordering::Relaxed, + ); + I3C2_LAST_STATUS_EN.store( + regs.i3cd040().read().bits(), + core::sync::atomic::Ordering::Relaxed, + ); + I3C2_LAST_SIGNAL_EN.store( + regs.i3cd044().read().bits(), + core::sync::atomic::Ordering::Relaxed, + ); + I3C2_IRQ_COUNT.fetch_add(1, core::sync::atomic::Ordering::Relaxed); + ast10x0_peripherals::i3c::dispatch_i3c_irq(2); +} + +codegen::declare_kernel_interrupt_handlers!(); + impl TargetInterface for Target { const NAME: &'static str = "AST10x0 Kernel I3C IBI (controller)";