use alloc::{collections::VecDeque, string::String, sync::Arc};
use core::{
sync::atomic::{AtomicBool, AtomicPtr, Ordering},
time::Duration,
};
use ax_kspin::SpinNoIrq;
use ax_memory_addr::PhysAddr;
use ax_task::WaitQueue;
use sg2002_tpu::{
ion::IonBuffer,
tpu::{
Sg2002Tpu,
error::TpuError,
types::{
CVITPU_DMABUF_FLUSH, CVITPU_DMABUF_FLUSH_FD, CVITPU_DMABUF_INVLD,
CVITPU_DMABUF_INVLD_FD, CVITPU_LOAD_TEE, CVITPU_PIO_MODE, CVITPU_SUBMIT_DMABUF,
CVITPU_SUBMIT_TEE, CVITPU_UNLOAD_TEE, CVITPU_WAIT_DMABUF, CviCacheOpArg,
CviSubmitDmaArg, CviWaitDmaArg,
},
},
};
use crate::{
file::{get_file_like, ion::IonBufferFile},
pseudofs::{
DeviceOps,
dev::{IrqRegistration, request_shared_disabled},
},
};
struct TpuTask {
tid: u64,
seq_no: u32,
vaddr: usize,
paddr: u64,
_buffer: Arc<IonBuffer>,
ret: i32,
}
static TASK_LIST: SpinNoIrq<VecDeque<TpuTask>> = SpinNoIrq::new(VecDeque::new());
static DONE_LIST: SpinNoIrq<VecDeque<TpuTask>> = SpinNoIrq::new(VecDeque::new());
const DONE_LIST_MAX: usize = 64;
static TASK_WQ: WaitQueue = WaitQueue::new();
static DONE_WQ: WaitQueue = WaitQueue::new();
static IRQ_WQ: WaitQueue = WaitQueue::new();
static WORKER_SPAWNED: AtomicBool = AtomicBool::new(false);
static HW_PTR: AtomicPtr<Sg2002Tpu> = AtomicPtr::new(core::ptr::null_mut());
pub struct TpuDevice {
hw: Arc<Sg2002Tpu>,
resource: TpuResource,
irq_registration: Option<IrqRegistration>,
}
const TPU_COMPATIBLES: &[&str] = &["cvitek,tpu"];
const TPU_TDMA_IRQ_NAME: &str = "tdma_irq";
const TPU_DEFAULT_MMIO_SIZE: usize = 0x1000;
const TPU_WAIT_TIMEOUT: Duration = Duration::from_secs(10);
#[derive(Clone, Copy)]
struct TpuResource {
tdma_paddr: usize,
tdma_size: usize,
tiu_paddr: usize,
tiu_size: usize,
irq: Option<ax_runtime::hal::irq::IrqId>,
}
impl TpuResource {
fn probe() -> Option<Self> {
let resource = Self::from_fdt();
if resource.is_none() {
warn!("[TPU] cvitek,tpu node not found or invalid in FDT");
}
resource
}
fn from_fdt() -> Option<Self> {
rdrive::with_fdt(|fdt| {
fdt.find_compatible(TPU_COMPATIBLES)
.into_iter()
.find_map(Self::from_fdt_node)
})
.flatten()
}
fn from_fdt_node(node: rdrive::probe::fdt::NodeType<'_>) -> Option<Self> {
if matches!(
node.as_node().status(),
Some(rdrive::probe::fdt::Status::Disabled)
) {
return None;
}
let mut regs = node.regs().into_iter();
let tdma = regs.next()?;
let tiu = regs.next()?;
let irq = match resolve_named_fdt_irq(&node, TPU_TDMA_IRQ_NAME) {
Ok(irq) => irq,
Err(err) => {
warn!("[TPU] failed to resolve {TPU_TDMA_IRQ_NAME}: {err:?}");
return None;
}
};
Some(Self {
tdma_paddr: tdma.address as usize,
tdma_size: tdma.size.unwrap_or(TPU_DEFAULT_MMIO_SIZE as u64) as usize,
tiu_paddr: tiu.address as usize,
tiu_size: tiu.size.unwrap_or(TPU_DEFAULT_MMIO_SIZE as u64) as usize,
irq,
})
}
}
fn resolve_named_fdt_irq(
node: &rdrive::probe::fdt::NodeType<'_>,
name: &str,
) -> Result<Option<ax_runtime::hal::irq::IrqId>, ax_runtime::hal::irq::IrqError> {
let Some(irq) = ax_driver::binding_irq_from_named_fdt_interrupt(node, name)
.map_err(|_| ax_runtime::hal::irq::IrqError::Unsupported)?
else {
return Ok(None);
};
ax_runtime::irq::resolve_binding_irq(irq).map(Some)
}
fn map_tpu_mmio(resource: TpuResource) -> Option<(*mut u8, *mut u8)> {
let tdma = match axklib::mem::iomap(PhysAddr::from(resource.tdma_paddr), resource.tdma_size) {
Ok(vaddr) => vaddr.as_mut_ptr(),
Err(err) => {
warn!(
"[TPU] failed to map TDMA MMIO at {:#x}+{:#x}: {err:?}",
resource.tdma_paddr, resource.tdma_size
);
return None;
}
};
let tiu = match axklib::mem::iomap(PhysAddr::from(resource.tiu_paddr), resource.tiu_size) {
Ok(vaddr) => vaddr.as_mut_ptr(),
Err(err) => {
warn!(
"[TPU] failed to map TIU MMIO at {:#x}+{:#x}: {err:?}",
resource.tiu_paddr, resource.tiu_size
);
return None;
}
};
Some((tdma, tiu))
}
fn register_tpu_irq(
irq: Option<ax_runtime::hal::irq::IrqId>,
hw: &Arc<Sg2002Tpu>,
) -> Option<IrqRegistration> {
let Some(irq) = irq else {
warn!("[TPU] TDMA IRQ not available; execution will use MMIO poll fallback");
return None;
};
let hw = Arc::clone(hw);
let registration = match request_shared_disabled(irq, move |_| {
if hw.handle_irq() {
warn!("[TPU] TDMA IRQ {irq:?} reports error status");
}
IRQ_WQ.notify_all(false);
ax_runtime::hal::irq::IrqReturn::Handled
}) {
Ok(registration) => registration,
Err(err) => {
warn!("[TPU] failed to register TDMA IRQ {irq:?}: {err:?}");
return None;
}
};
if let Err(err) = registration.enable() {
warn!("[TPU] failed to enable TDMA IRQ {irq:?}: {err:?}");
return None;
}
info!("[TPU] TDMA IRQ {irq:?} registered and enabled");
Some(registration)
}
fn tpu_wait_irq(timeout_us: u64) -> bool {
let hw = HW_PTR.load(Ordering::Acquire);
if hw.is_null() {
return false;
}
let hw = unsafe { &*hw };
!IRQ_WQ.wait_timeout_until(Duration::from_micros(timeout_us), || hw.irq_pending())
}
fn tpu_worker(hw: Arc<Sg2002Tpu>) {
info!("[TPU] worker thread started");
loop {
let mut task = loop {
if let Some(task) = TASK_LIST.lock().pop_front() {
break task;
}
TASK_WQ.wait_until(|| !TASK_LIST.lock().is_empty());
};
task.ret = hw
.run_one(task.seq_no, task.vaddr, task.paddr)
.map_or(-1, |_| 0);
{
let mut done = DONE_LIST.lock();
done.push_back(task);
while done.len() > DONE_LIST_MAX {
let dropped = done.pop_front();
if let Some(t) = dropped {
warn!(
"[TPU] done list full, dropping orphaned result (tid={}, seq_no={})",
t.tid, t.seq_no
);
}
}
}
DONE_WQ.notify_all(false);
}
}
impl TpuDevice {
pub fn probe() -> Option<Self> {
let resource = TpuResource::probe()?;
let hw = {
let (tdma, tiu) = map_tpu_mmio(resource)?;
Arc::new(unsafe { Sg2002Tpu::from_vaddr(tdma, tiu) })
};
Some(Self::setup(hw, resource))
}
fn setup(hw: Arc<Sg2002Tpu>, resource: TpuResource) -> Self {
hw.set_wait_irq_fn(tpu_wait_irq);
if let Err(err) = hw.init() {
warn!("[TPU] init failed: {:?}", err);
}
let irq_registration = register_tpu_irq(resource.irq, &hw);
info!(
"[TPU] resource tdma=[{:#x}, +{:#x}) tiu=[{:#x}, +{:#x}) irq={:?} irq_wait={} \
source=fdt",
resource.tdma_paddr,
resource.tdma_size,
resource.tiu_paddr,
resource.tiu_size,
resource.irq,
irq_registration.is_some(),
);
if WORKER_SPAWNED
.compare_exchange(false, true, Ordering::AcqRel, Ordering::Acquire)
.is_ok()
{
HW_PTR.store(Arc::as_ptr(&hw) as *mut Sg2002Tpu, Ordering::Release);
let worker_hw = hw.clone();
ax_task::spawn_with_name(move || tpu_worker(worker_hw), String::from("tpu-worker"));
}
Self {
hw,
resource,
irq_registration,
}
}
fn submit_dmabuf(&self, arg: usize) -> Result<usize, TpuError> {
let submit_arg = unsafe { &*(arg as *const CviSubmitDmaArg) };
debug!(
"[TPU] submit dmabuf: fd={}, seq_no={}",
submit_arg.fd, submit_arg.seq_no
);
if self.irq_registration.is_none() {
warn!("[TPU] TDMA IRQ {:?} not registered", self.resource.irq);
}
let fd = submit_arg.fd;
let file = get_file_like(fd).map_err(|_| {
error!("[TPU] Failed to get file for fd={}", fd);
TpuError::InvalidDmabuf
})?;
let ion_file: Arc<IonBufferFile> = file.downcast_arc::<IonBufferFile>().map_err(|_| {
error!("[TPU] fd={} is not an IonBufferFile", fd);
TpuError::InvalidDmabuf
})?;
let buffer = ion_file.buffer().clone();
debug!(
"[TPU] dmabuf info: handle={}, size={}, paddr=0x{:x}",
buffer.handle.as_u32(),
buffer.size,
buffer.dma_info.bus_addr.as_u64()
);
let task = TpuTask {
tid: ax_task::current().id().as_u64(),
seq_no: submit_arg.seq_no,
vaddr: buffer.dma_info.cpu_addr.as_ptr() as usize,
paddr: buffer.dma_info.bus_addr.as_u64(),
_buffer: buffer,
ret: 0,
};
TASK_LIST.lock().push_back(task);
TASK_WQ.notify_one(true);
Ok(0)
}
fn wait_dmabuf(&self, arg: usize) -> Result<usize, TpuError> {
let wait_arg = unsafe { &mut *(arg as *mut CviWaitDmaArg) };
let seq_no = wait_arg.seq_no;
let tid = ax_task::current().id().as_u64();
let timed_out = DONE_WQ.wait_timeout_until(TPU_WAIT_TIMEOUT, || {
DONE_LIST
.lock()
.iter()
.any(|t| t.tid == tid && t.seq_no == seq_no)
});
let found = {
let mut done = DONE_LIST.lock();
done.iter()
.position(|t| t.tid == tid && t.seq_no == seq_no)
.map(|idx| done.remove(idx).unwrap())
};
match found {
Some(task) => {
wait_arg.ret = task.ret;
if task.ret != 0 {
return Err(TpuError::Timeout);
}
Ok(0)
}
None => {
wait_arg.ret = -1;
warn!(
"[TPU] wait dmabuf: (tid={}, seq_no={}) not found (timed_out={})",
tid, seq_no, timed_out
);
Err(TpuError::Timeout)
}
}
}
fn cache_flush(&self, arg: usize) -> Result<usize, TpuError> {
let flush_arg = unsafe { &*(arg as *const CviCacheOpArg) };
self.hw.cache_flush_paddr(flush_arg.paddr, flush_arg.size)?;
Ok(0)
}
fn cache_invalidate(&self, arg: usize) -> Result<usize, TpuError> {
let invalidate_arg = unsafe { &*(arg as *const CviCacheOpArg) };
self.hw
.cache_invalidate_paddr(invalidate_arg.paddr, invalidate_arg.size)?;
Ok(0)
}
fn dmabuf_flush_fd(&self, arg: usize) -> Result<usize, TpuError> {
let fd = arg as i32;
debug!("TPU dmabuf flush fd: {}", fd);
let buffer = self.lookup_ion_buffer(fd)?;
let paddr = buffer.dma_info.bus_addr.as_u64();
let size = buffer.size as u64;
self.hw.cache_flush_paddr(paddr, size)?;
debug!("Flushed buffer: paddr=0x{:x}, size={}", paddr, size);
Ok(0)
}
fn dmabuf_invld_fd(&self, arg: usize) -> Result<usize, TpuError> {
let fd = arg as i32;
debug!("TPU dmabuf invalidate fd: {}", fd);
let buffer = self.lookup_ion_buffer(fd)?;
let paddr = buffer.dma_info.bus_addr.as_u64();
let size = buffer.size as u64;
self.hw.cache_invalidate_paddr(paddr, size)?;
Ok(0)
}
fn lookup_ion_buffer(&self, fd: i32) -> Result<Arc<IonBuffer>, TpuError> {
let file = get_file_like(fd).map_err(|err| {
error!("[TPU] failed to get file for fd={}: {:?}", fd, err);
TpuError::InvalidDmabuf
})?;
let ion_file: Arc<IonBufferFile> = file.downcast_arc::<IonBufferFile>().map_err(|_| {
error!("[TPU] fd={} is not an IonBufferFile", fd);
TpuError::InvalidDmabuf
})?;
Ok(ion_file.buffer().clone())
}
}
impl DeviceOps for TpuDevice {
fn read_at(&self, _buf: &mut [u8], _offset: u64) -> axfs_ng_vfs::VfsResult<usize> {
Ok(0)
}
fn write_at(&self, _buf: &[u8], _offset: u64) -> axfs_ng_vfs::VfsResult<usize> {
Ok(0)
}
fn ioctl(&self, cmd: u32, arg: usize) -> axfs_ng_vfs::VfsResult<usize> {
debug!("TPU ioctl: cmd=0x{:x}, arg=0x{:x}", cmd, arg);
let result = match cmd {
CVITPU_SUBMIT_DMABUF => self.submit_dmabuf(arg),
CVITPU_DMABUF_FLUSH_FD => self.dmabuf_flush_fd(arg),
CVITPU_DMABUF_INVLD_FD => self.dmabuf_invld_fd(arg),
CVITPU_DMABUF_FLUSH => self.cache_flush(arg),
CVITPU_DMABUF_INVLD => self.cache_invalidate(arg),
CVITPU_WAIT_DMABUF => self.wait_dmabuf(arg),
CVITPU_PIO_MODE => {
warn!("TPU PIO mode not implemented");
Ok(0)
}
CVITPU_LOAD_TEE | CVITPU_SUBMIT_TEE | CVITPU_UNLOAD_TEE => {
warn!("TPU TEE operations not supported");
Err(TpuError::NotInitialized)
}
_ => {
warn!("Unknown TPU ioctl command: 0x{:x}", cmd);
Err(TpuError::NotInitialized)
}
};
match result {
Ok(v) => Ok(v),
Err(e) => {
error!("TPU ioctl error: {:?}", e);
Err(ax_errno::AxError::Unsupported)
}
}
}
fn as_any(&self) -> &dyn core::any::Any {
self
}
}