blob: e8f5a42a9c2cc7982f68365330d921a0e6dbd5c7 [file]
// Licensed under the Apache-2.0 license
//! I3C In-Band Interrupt (IBI) Work Queue
//!
//! Handles IBI events including Hot-Join, SIR (Slave Interrupt Request),
//! and target dynamic address assignment.
use core::cell::RefCell;
use critical_section::Mutex;
use heapless::spsc::Queue;
/// IBI queue depth
const IBIQ_DEPTH: usize = 16;
/// Maximum IBI payload data size
const IBI_DATA_MAX: u8 = 16;
// =============================================================================
// IBI Work Item
// =============================================================================
/// IBI work item representing an interrupt event
#[derive(Debug, Clone, Copy)]
pub enum IbiWork {
/// Hot-Join request from a device
HotJoin,
/// Slave Interrupt Request
Sirq {
/// Address of requesting device
addr: u8,
/// Length of payload data
len: u8,
/// Payload data
data: [u8; IBI_DATA_MAX as usize],
},
/// Target dynamic address assignment notification
TargetDaAssignment,
}
// =============================================================================
// Static Queue 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.
///
/// 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;
};
critical_section::with(|cs| {
let mut b = workq.borrow(cs).borrow_mut();
if b.prod.is_none() || b.cons.is_none() {
// SAFETY: Each bus's queue is only split once and we're in a critical section.
// The static mut is only accessed here, protected by the Mutex + critical section.
let (p, c) = unsafe { IBIQ_BUFS[bus].split() };
b.prod = Some(p);
b.cons = Some(c);
}
});
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)?;
critical_section::with(|cs| {
let mut b = workq.borrow(cs).borrow_mut();
b.cons.take()
})
}
// =============================================================================
// Enqueue Functions
// =============================================================================
/// 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
})
}
/// 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
})
}
/// 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
})
}