use alloc::{sync::Arc, vec::Vec};
use core::{any::Any, ffi::c_int, mem::size_of};
use ax_driver::jpeg::{self, ResolvedDmaBuf, mpp, registers};
use axfs_ng_vfs::{DeviceId, VfsError, VfsResult};
use crate::{
file::dmabuf::resolve_contiguous_dmabuf,
mm::{UserConstPtr, UserPtr},
pseudofs::DeviceOps,
sync::Mutex,
};
pub const MPP_SERVICE_DEVICE_ID: DeviceId = DeviceId::new(0xF1, 0x10);
const DECODE_TIMEOUT_NS: u64 = 100_000_000;
struct TaskState {
session: mpp::MppSession,
read_dst: usize,
}
pub struct MppService {
state: Mutex<TaskState>,
}
impl MppService {
pub fn new() -> Self {
Self {
state: Mutex::new(TaskState {
session: mpp::MppSession::new(),
read_dst: 0,
}),
}
}
}
impl Default for MppService {
fn default() -> Self {
Self::new()
}
}
impl DeviceOps for MppService {
fn read_at(&self, _buf: &mut [u8], _offset: u64) -> VfsResult<usize> {
Ok(0)
}
fn write_at(&self, buf: &[u8], _offset: u64) -> VfsResult<usize> {
Ok(buf.len())
}
fn as_any(&self) -> &dyn Any {
self
}
fn ioctl(&self, current: &crate::task::UserTaskRef, cmd: u32, arg: usize) -> VfsResult<usize> {
if cmd != mpp::MPP_IOC_CFG_V1 {
return Err(VfsError::NotATty);
}
if arg == 0 {
return Err(VfsError::InvalidInput);
}
let mut state = self.state.lock();
for i in 0..mpp::MAX_MSG_NUM {
let req = read_request(current, arg + i * size_of::<mpp::MppRequest>())?;
handle_request(current, &mut state, &req)?;
if req.flag & mpp::flags::LAST_MSG != 0 || req.flag & mpp::flags::MULTI_MSG == 0 {
break;
}
}
Ok(0)
}
}
fn read_request(current: &crate::task::UserTaskRef, uaddr: usize) -> VfsResult<mpp::MppRequest> {
unsafe { UserConstPtr::<mpp::MppRequest>::from(uaddr).read_abi(current) }
.map_err(|_| VfsError::InvalidData)
}
fn write_u32_to_user(
current: &crate::task::UserTaskRef,
uaddr: usize,
value: u32,
) -> VfsResult<()> {
UserPtr::<u32>::from(uaddr)
.write(current, value)
.map_err(|_| VfsError::InvalidData)
}
fn handle_request(
current: &crate::task::UserTaskRef,
state: &mut TaskState,
req: &mpp::MppRequest,
) -> VfsResult<()> {
let data = req.data_ptr as usize;
match req.cmd {
mpp::cmd::PROBE_HW_SUPPORT => {
write_u32_to_user(current, data, mpp::HW_SUPPORT_JPEG_DEC)?;
}
mpp::cmd::QUERY_HW_ID => {
write_u32_to_user(current, data, jpeg::read_id().unwrap_or(0))?;
}
mpp::cmd::QUERY_CMD_SUPPORT => {
write_u32_to_user(current, data, 0)?;
}
mpp::cmd::INIT_CLIENT_TYPE => {
let client = UserConstPtr::<u32>::from(data)
.read(current)
.map_err(|_| VfsError::InvalidData)?;
state
.session
.init_client_type(client)
.map_err(|_| VfsError::InvalidInput)?;
}
mpp::cmd::SET_REG_WRITE => {
let n = (req.size as usize / 4).min(registers::REG_COUNT);
let words = UserConstPtr::<u32>::from(data)
.read_slice(current, n)
.map_err(|_| VfsError::InvalidData)?;
state.session.set_reg_write(&words);
}
mpp::cmd::SET_REG_READ => {
state.read_dst = data;
state.session.set_reg_read(req.offset, req.size);
}
mpp::cmd::SET_REG_ADDR_OFFSET => {
let elem = size_of::<mpp::RegOffset>();
let cnt = (req.size as usize / elem).min(mpp::MAX_REG_OFFSETS);
let elems =
unsafe { UserConstPtr::<mpp::RegOffset>::from(data).read_abi_slice(current, cnt) }
.map_err(|_| VfsError::InvalidData)?;
state
.session
.add_reg_offsets(&elems)
.map_err(|_| VfsError::InvalidInput)?;
}
mpp::cmd::POLL_HW_FINISH | mpp::cmd::POLL_HW_IRQ => {
run_decode(current, state)?;
}
mpp::cmd::RESET_SESSION => {
state.session.reset();
state.read_dst = 0;
}
_ => {}
}
Ok(())
}
fn run_decode(current: &crate::task::UserTaskRef, state: &mut TaskState) -> VfsResult<()> {
let mut imported = Vec::with_capacity(registers::ADDR_REG_INDICES.len());
state
.session
.resolve_addresses(|fd| resolve_fd(fd, &mut imported))
.map_err(|_| VfsError::InvalidInput)?;
let mut readback = [0u32; registers::REG_COUNT];
jpeg::run_raw(state.session.regs(), &mut readback, DECODE_TIMEOUT_NS).map_err(map_jpeg_err)?;
let (first, count) = state.session.read_window();
if state.read_dst != 0 && count > 0 && first < registers::REG_COUNT {
let count = count.min(registers::REG_COUNT - first);
UserPtr::<u32>::from(state.read_dst)
.write_slice(current, &readback[first..first + count])
.map_err(|_| VfsError::InvalidData)?;
}
state.session.clear_task();
state.read_dst = 0;
Ok(())
}
fn resolve_fd(
fd: u32,
imported: &mut Vec<Arc<crate::file::dmabuf::DmaBufFile>>,
) -> Option<ResolvedDmaBuf> {
let Some(buf) = resolve_contiguous_dmabuf(fd as c_int) else {
warn!("mpp_service: register fd {fd} is not a resolvable dma-buf");
return None;
};
let phys = buf.phys_base();
match u32::try_from(phys) {
Ok(address) => {
let size = buf.size();
imported.push(buf);
Some(ResolvedDmaBuf { address, size })
}
Err(_) => {
warn!("mpp_service: dma-buf fd {fd} phys {phys:#x} exceeds 32-bit JPU range");
None
}
}
}
fn map_jpeg_err(err: jpeg::Error) -> VfsError {
match err {
jpeg::Error::Timeout => VfsError::TimedOut,
_ => VfsError::InvalidData,
}
}