Address I3C review follow-ups
diff --git a/target/ast10x0/peripherals/i3c/constants.rs b/target/ast10x0/peripherals/i3c/constants.rs index 45aaeb0..27e59d5 100644 --- a/target/ast10x0/peripherals/i3c/constants.rs +++ b/target/ast10x0/peripherals/i3c/constants.rs
@@ -262,12 +262,10 @@ pub const I3C_DEFAULT_STATIC_ADDR: u8 = 0x74; /// One-second operation timeout expressed in microseconds. pub const I3C_OP_TIMEOUT_US: u32 = USEC_PER_SEC; +/// Bring-up reset poll delay between iterations in nanoseconds. +pub const I3C_INIT_POLL_DELAY_NS: u32 = 100_000; /// Generic bounded-poll iteration ceiling used by controller bring-up waits. pub const I3C_POLL_MAX_ITERS: u32 = 1_000_000; -/// Generic bounded-poll delay between iterations in nanoseconds. -pub const I3C_POLL_DELAY_NS: u32 = 10_000; -/// IBI enable / halt / reset-control poll ceiling. -pub const I3C_CTRL_POLL_MAX_ITERS: u32 = 1_000_000; /// Queue reset / halt / IBI enable poll delay in nanoseconds. pub const I3C_CTRL_POLL_DELAY_NS: u32 = 10_000; /// Program the maximum IBI data threshold supported by the controller.
diff --git a/target/ast10x0/peripherals/i3c/hardware.rs b/target/ast10x0/peripherals/i3c/hardware.rs index f150e44..4924c96 100644 --- a/target/ast10x0/peripherals/i3c/hardware.rs +++ b/target/ast10x0/peripherals/i3c/hardware.rs
@@ -43,18 +43,18 @@ 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_CTRL_POLL_DELAY_NS, I3C_CTRL_POLL_MAX_ITERS, I3C_DEFAULT_STATIC_ADDR, - I3C_GLOBAL_RESET_DEASSERT_MASK, I3C_IBI_DATA_THRESHOLD_MAX, I3C_INTR_STATUS_ALL_BITS, - I3C_MSG_READ, I3C_OP_TIMEOUT_US, I3C_POLL_DELAY_NS, I3C_POLL_MAX_ITERS, - 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, bit, field_get, field_prep, + I3C_CTRL_POLL_DELAY_NS, I3C_DEFAULT_STATIC_ADDR, I3C_GLOBAL_RESET_DEASSERT_MASK, + I3C_IBI_DATA_THRESHOLD_MAX, I3C_INIT_POLL_DELAY_NS, I3C_INTR_STATUS_ALL_BITS, I3C_MSG_READ, + I3C_OP_TIMEOUT_US, I3C_POLL_MAX_ITERS, 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, bit, + field_get, field_prep, }; use super::error::I3cError as I3cDrvError; use super::error::I3cError; @@ -783,7 +783,7 @@ || regs.i3cd034().read().bits(), |val| val == 0, &mut self.yield_fn, - I3C_POLL_DELAY_NS, + I3C_INIT_POLL_DELAY_NS, I3C_POLL_MAX_ITERS, ); @@ -910,6 +910,9 @@ } fn enable_irq(&mut self) { + // The integration layer owns the top-level vector and should point it + // at `dispatch_i3c_irq(bus)`. This helper only unmasks the bus IRQ + // line after registration + hardware init have completed. unsafe { match I3C::BUS_NUM { 0 => NVIC::unmask(ast1060_pac::Interrupt::i3c), @@ -1288,7 +1291,7 @@ |val| val != u32::from(expected), &mut self.yield_fn, I3C_CTRL_POLL_DELAY_NS, - I3C_CTRL_POLL_MAX_ITERS, + I3C_POLL_MAX_ITERS, ); if rc.is_err() { @@ -1313,7 +1316,7 @@ |val| val == u32::from(expected), &mut self.yield_fn, I3C_CTRL_POLL_DELAY_NS, - I3C_CTRL_POLL_MAX_ITERS, + I3C_POLL_MAX_ITERS, ); if rc.is_err() { @@ -1335,7 +1338,7 @@ |val| val == 0, &mut self.yield_fn, I3C_CTRL_POLL_DELAY_NS, - I3C_CTRL_POLL_MAX_ITERS, + I3C_POLL_MAX_ITERS, ); if rc.is_err() { @@ -1473,11 +1476,14 @@ return; } - // SAFETY: `curr_xfer` is written by `start_xfer` from a unique `&mut I3cXfer` - // for the duration of the active transfer, and is reset back to null exactly - // once here with `swap(..., AcqRel)` before reconstructing the reference. - // The caller waits for transfer completion before dropping the stack-owned - // `xfer`, so the pointed-to object remains valid for this ISR handoff. + // SAFETY: `curr_xfer` is published by `start_xfer` from a unique + // `&mut I3cXfer`. The ISR path here and the timeout cleanup paths both + // compete via `swap(null, AcqRel)`; only the side that observes a + // non-null pointer may reconstruct and use it, while the loser sees + // null and performs no dereference. This target runs the handoff on a + // single core, and the owning thread waits for completion or timeout + // before dropping the stack-owned `xfer`, so the pointee outlives this + // exclusive ownership transfer. let xfer: &mut I3cXfer = unsafe { &mut *(p.cast::<I3cXfer>()) }; let nresp = self.i3c().i3cd04c().read().respbufblr().bits() as usize;
diff --git a/target/ast10x0/peripherals/i3c/ibi.rs b/target/ast10x0/peripherals/i3c/ibi.rs index 3e590fd..1c382d5 100644 --- a/target/ast10x0/peripherals/i3c/ibi.rs +++ b/target/ast10x0/peripherals/i3c/ibi.rs
@@ -10,12 +10,13 @@ //! //! **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. +//! `Mutex<UnsafeCell<..>>`. On this target (heapless 0.9 + this toolchain) we +//! observed unstable behavior when those handles were stored in a `static` and +//! later 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`. The root cause was +//! not fully isolated, so this port uses a simpler fixed-size ring buffer whose +//! aliasing and lifetime rules are easier to audit. //! //! 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 @@ -25,7 +26,7 @@ //! API (`i3c_ibi_workq_consumer().dequeue()` + the three enqueue functions) is //! unchanged. -use core::cell::RefCell; +use core::cell::UnsafeCell; use critical_section::Mutex; /// IBI queue depth @@ -116,25 +117,33 @@ } } -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())), +static IBI_RINGS: [Mutex<UnsafeCell<IbiRing>>; 4] = [ + Mutex::new(UnsafeCell::new(IbiRing::new())), + Mutex::new(UnsafeCell::new(IbiRing::new())), + Mutex::new(UnsafeCell::new(IbiRing::new())), + Mutex::new(UnsafeCell::new(IbiRing::new())), ]; /// Run `f` against the ring for `bus`, serialized by the critical section. /// /// Returns `None` if `bus` is out of range. +/// +/// # Safety Contract +/// +/// `f` must not re-enter this module's queue API (`with_ring`, `dequeue`, or +/// any of the enqueue helpers). Nested `critical_section::with(...)` calls are +/// legal on this target, so re-entering while `ring: &mut IbiRing` is live +/// would violate the exclusive-borrow assumption documented below. 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() }; + // SAFETY: each ring is reachable only through this helper and wrapped + // in a `critical_section::Mutex`. While the critical section is held + // there is no ISR/thread concurrency. The caller-provided closure is + // also required not to re-enter this module's queue API while the + // mutable borrow is live, so it is sound to project the + // `UnsafeCell<IbiRing>` to `&mut IbiRing`. + let ring: &mut IbiRing = unsafe { &mut *workq.borrow(cs).get() }; f(ring) })) }