use alloc::{borrow::Cow, collections::btree_map::BTreeMap, sync::Arc, vec::Vec};
use core::{any::Any, ffi::c_int, task::Context};
use ax_errno::AxResult;
use ax_sync::Mutex;
use axfs_ng_vfs::{NodeFlags, VfsError, VfsResult};
use axpoll::{IoEvents, Pollable};
use rockchip_rga::{
RgaVersion, RockchipRga,
backend::RgaStatus,
librga_abi,
operation::{ImageDesc, RgaOperation},
};
use starry_vm::{VmMutPtr, VmPtr};
use crate::{
file::{
File as KernelFile, FileLike, IoDst, IoSrc, Kstat,
dmabuf::{DmaBufFile, resolve_contiguous_dmabuf},
},
pseudofs::DeviceOps,
task::AsThread,
};
const RGA_BUFFER_POOL_SIZE_MAX: u32 = 40;
const RGA_TASK_NUM_MAX: u32 = 256;
struct ImportedBuf {
phys_addr: u64,
len: u64,
obj: Option<Arc<DmaBufFile>>,
}
pub(crate) struct RgaDevice;
impl RgaDevice {
pub fn new() -> Self {
Self
}
}
impl Default for RgaDevice {
fn default() -> Self {
Self::new()
}
}
impl DeviceOps for RgaDevice {
fn read_at(&self, _buf: &mut [u8], _offset: u64) -> VfsResult<usize> {
Err(VfsError::InvalidInput)
}
fn write_at(&self, _buf: &[u8], _offset: u64) -> VfsResult<usize> {
Err(VfsError::InvalidInput)
}
fn ioctl(&self, _cmd: u32, _arg: usize) -> VfsResult<usize> {
Err(VfsError::NotATty)
}
fn as_any(&self) -> &dyn Any {
self
}
fn flags(&self) -> NodeFlags {
NodeFlags::NON_CACHEABLE
}
}
pub(crate) fn is_rga_device(inner: &dyn Any) -> bool {
inner.is::<RgaDevice>()
}
pub(crate) fn open_rga_file(file: ax_fs_ng::File, open_flags: u32) -> AxResult<Arc<dyn FileLike>> {
Ok(Arc::new(RgaFile::new(KernelFile::new(file, open_flags))))
}
struct RgaFile {
base: KernelFile,
handle_table: Mutex<BTreeMap<u32, ImportedBuf>>,
next_handle: Mutex<u32>,
requests: Mutex<BTreeMap<u32, Vec<librga_abi::RgaReq>>>,
next_request_id: Mutex<u32>,
}
impl RgaFile {
fn new(base: KernelFile) -> Self {
Self {
base,
handle_table: Mutex::new(BTreeMap::new()),
next_handle: Mutex::new(1),
requests: Mutex::new(BTreeMap::new()),
next_request_id: Mutex::new(1),
}
}
fn alloc_handle(&self, entry: ImportedBuf) -> VfsResult<u32> {
let mut table = self.handle_table.lock();
let mut next = self.next_handle.lock();
for _ in 0..=u32::MAX {
let h = *next;
*next = h.wrapping_add(1);
if h == 0 || table.contains_key(&h) {
continue;
}
table.insert(h, entry);
return Ok(h);
}
Err(VfsError::NoMemory)
}
fn resolve_buf(
&self,
raw: u64,
handle_flag: bool,
) -> VfsResult<(u64, u64, Option<Arc<DmaBufFile>>)> {
if raw == 0 {
return Ok((0, 0, None));
}
if handle_flag {
let handle = raw as u32;
let table = self.handle_table.lock();
let entry = table.get(&handle).ok_or(VfsError::BadFileDescriptor)?;
Ok((entry.phys_addr, entry.len, entry.obj.clone()))
} else {
let obj = resolve_contiguous_dmabuf(raw as c_int).ok_or(VfsError::BadFileDescriptor)?;
let phys = obj.phys_base() as u64;
let len = obj.size() as u64;
Ok((phys, len, Some(obj)))
}
}
fn handle_blit_sync(&self, arg: usize) -> VfsResult<usize> {
let req: librga_abi::RgaReq = unsafe {
(arg as *const librga_abi::RgaReq)
.vm_read_uninit()?
.assume_init()
};
self.execute_blit(&req)
}
fn execute_blit(&self, req: &librga_abi::RgaReq) -> VfsResult<usize> {
let parsed = librga_abi::parse(req).map_err(|e| {
warn!("RGA_BLIT: rejecting unsupported request: {e:?}");
VfsError::InvalidInput
})?;
let handle_flag = req.handle_flag != 0;
let is_fill = matches!(parsed.kind, librga_abi::ParsedKind::Fill);
let (src_phys, src_len, src_keep) = if is_fill {
(0, 0, None)
} else {
self.resolve_buf(parsed.src.addr, handle_flag)?
};
let (src_uv_phys, src_uv_len, src_uv_keep) = if !is_fill && parsed.src.uv_addr != 0 {
let (p, l, k) = self.resolve_buf(parsed.src.uv_addr, handle_flag)?;
(Some(p), Some(l), k)
} else {
(None, None, None)
};
let (dst_phys, dst_len, dst_keep) = self.resolve_buf(parsed.dst.addr, handle_flag)?;
let (dst_uv_phys, dst_uv_len, dst_uv_keep) = if parsed.dst.uv_addr != 0 {
let (p, l, k) = self.resolve_buf(parsed.dst.uv_addr, handle_flag)?;
(Some(p), Some(l), k)
} else {
(None, None, None)
};
let op = match parsed.into_operation(src_phys, src_uv_phys, dst_phys, dst_uv_phys) {
Ok(o) => o,
Err(e) => {
warn!("RGA_BLIT into_operation FAIL {:?}", e);
return Err(VfsError::InvalidInput);
}
};
Self::check_bounds(
&op,
(src_phys, src_len),
src_uv_len,
(dst_phys, dst_len),
dst_uv_len,
)?;
let devs = rdrive::get_list::<RockchipRga>();
if devs.is_empty() {
return Err(VfsError::NoSuchDevice);
}
let mut guard = 'acquire: {
for d in devs.iter() {
let guard = match d.try_lock() {
Ok(g) => g,
Err(_) => match d.lock() {
Ok(g) => g,
Err(_) => continue, },
};
if guard
.cores()
.iter()
.any(|c| c.config().version == RgaVersion::Rga2)
{
break 'acquire guard;
}
drop(guard);
}
return Err(VfsError::NoSuchDevice);
};
let rga = &mut *guard;
let core = rga
.cores_mut()
.iter_mut()
.find(|c| c.config().version == RgaVersion::Rga2)
.ok_or(VfsError::NoSuchDevice)?;
let _keep = (src_keep, src_uv_keep, dst_keep, dst_uv_keep);
if let Err(e) = core.start(&op) {
warn!("RGA_BLIT core.start failed: {:?}", e);
return Err(VfsError::InvalidInput);
}
for _ in 0..500 {
match core.poll_status() {
RgaStatus::Done => {
core.finish();
return Ok(0);
}
RgaStatus::Error => {
let d = core.diag();
core.finish();
warn!(
"RGA_BLIT poll=Error int=0x{:08x} status=0x{:08x} cmd_ctrl=0x{:08x}",
d.int, d.status, d.cmd_ctrl
);
return Err(VfsError::Io);
}
RgaStatus::Busy => {
ax_runtime::hal::time::busy_wait(core::time::Duration::from_micros(100));
}
}
}
let d = core.diag();
let _ = core.recover();
warn!(
"RGA_BLIT poll=Timeout int=0x{:08x} status=0x{:08x} cmd_ctrl=0x{:08x}",
d.int, d.status, d.cmd_ctrl
);
Err(VfsError::TimedOut)
}
fn check_bounds(
op: &RgaOperation,
src: (u64, u64),
src_uv_len: Option<u64>,
dst: (u64, u64),
dst_uv_len: Option<u64>,
) -> VfsResult<()> {
match op {
RgaOperation::Fill { dst: d, .. } => Self::check_desc(d, dst, dst_uv_len),
RgaOperation::Copy { src: s, dst: d } => {
Self::check_desc(s, src, src_uv_len)?;
Self::check_desc(d, dst, dst_uv_len)
}
RgaOperation::Blit(b) => {
Self::check_desc(&b.src, src, src_uv_len)?;
Self::check_desc(&b.dst, dst, dst_uv_len)
}
}
}
fn check_desc(desc: &ImageDesc, buf: (u64, u64), uv_sep_len: Option<u64>) -> VfsResult<()> {
let ext = desc.plane_extents().map_err(|_| VfsError::InvalidInput)?;
let (base, len) = buf;
if !Self::within(desc.phys_addr, ext.y, base, len) {
warn!("RGA_BLIT: luma/RGB plane addresses past its imported buffer");
return Err(VfsError::InvalidInput);
}
if let Some(uv_ext) = ext.uv {
let uv_base = desc.uv_phys_addr.ok_or(VfsError::InvalidInput)?;
let ok = if base.checked_add(ext.y) == Some(uv_base) {
Self::within(uv_base, uv_ext, base, len)
} else if let Some(uv_len) = uv_sep_len {
Self::within(uv_base, uv_ext, uv_base, uv_len)
} else {
false
};
if !ok {
warn!("RGA_BLIT: chroma plane addresses past its imported buffer");
return Err(VfsError::InvalidInput);
}
}
Ok(())
}
fn within(start: u64, ext: u64, base: u64, len: u64) -> bool {
start >= base
&& start
.checked_sub(base)
.and_then(|off| off.checked_add(ext))
.is_some_and(|end| end <= len)
}
fn handle_import_buffer(&self, arg: usize) -> VfsResult<usize> {
let pool: librga_abi::RgaBufferPool = unsafe {
(arg as *const librga_abi::RgaBufferPool)
.vm_read_uninit()?
.assume_init()
};
if pool.size == 0 || pool.size > RGA_BUFFER_POOL_SIZE_MAX || pool.buffers_ptr == 0 {
return Err(VfsError::InvalidInput);
}
let elem_size = core::mem::size_of::<librga_abi::RgaExternalBuffer>(); let base = pool.buffers_ptr as usize;
for i in 0..pool.size as usize {
let ptr = base + i * elem_size;
let mut ext: librga_abi::RgaExternalBuffer = unsafe {
(ptr as *const librga_abi::RgaExternalBuffer)
.vm_read_uninit()?
.assume_init()
};
let entry = match ext.r#type {
librga_abi::RGA_DMA_BUFFER => {
let obj = resolve_contiguous_dmabuf(ext.memory as c_int)
.ok_or(VfsError::BadFileDescriptor)?;
ImportedBuf {
phys_addr: obj.phys_base() as u64,
len: obj.size() as u64,
obj: Some(obj),
}
}
librga_abi::RGA_PHYSICAL_ADDRESS => {
if !ax_task::current().as_thread().cred().has_cap_sys_rawio() {
warn!(
"RGA_IOC_IMPORT_BUFFER: RGA_PHYSICAL_ADDRESS requires CAP_SYS_RAWIO; \
denied"
);
return Err(VfsError::OperationNotPermitted);
}
ImportedBuf {
phys_addr: ext.memory,
len: u64::MAX,
obj: None,
}
}
_ => return Err(VfsError::Unsupported),
};
let handle = self.alloc_handle(entry)?;
ext.handle = handle;
let res = (ptr as *mut librga_abi::RgaExternalBuffer).vm_write(ext);
if res.is_err() {
self.handle_table.lock().remove(&handle);
}
res?;
}
Ok(0)
}
fn handle_release_buffer(&self, arg: usize) -> VfsResult<usize> {
let pool: librga_abi::RgaBufferPool = unsafe {
(arg as *const librga_abi::RgaBufferPool)
.vm_read_uninit()?
.assume_init()
};
if pool.size == 0 || pool.size > RGA_BUFFER_POOL_SIZE_MAX || pool.buffers_ptr == 0 {
return Err(VfsError::InvalidInput);
}
let elem_size = core::mem::size_of::<librga_abi::RgaExternalBuffer>();
let base = pool.buffers_ptr as usize;
let mut table = self.handle_table.lock();
for i in 0..pool.size as usize {
let ptr = base + i * elem_size;
let ext: librga_abi::RgaExternalBuffer = unsafe {
(ptr as *const librga_abi::RgaExternalBuffer)
.vm_read_uninit()?
.assume_init()
};
if table.remove(&ext.handle).is_none() {
return Err(VfsError::NotFound);
}
}
Ok(0)
}
fn handle_get_driver_version(&self, arg: usize) -> VfsResult<usize> {
let mut v = librga_abi::RgaVersionT {
major: 1,
minor: 3,
revision: 1,
string: [0; 16],
};
v.string[..5].copy_from_slice(b"1.3.1");
(arg as *mut librga_abi::RgaVersionT).vm_write(v)?;
Ok(0)
}
fn handle_get_hw_version(&self, arg: usize) -> VfsResult<usize> {
let mut v0 = librga_abi::RgaVersionT {
major: 3,
minor: 2,
revision: 0x63318,
string: [0; 16],
};
v0.string[..8].copy_from_slice(b"3.2.0e63");
let mut hw = librga_abi::RgaHwVersions {
size: 1,
..Default::default()
};
hw.version[0] = v0;
(arg as *mut librga_abi::RgaHwVersions).vm_write(hw)?;
Ok(0)
}
fn handle_request_create(&self, arg: usize) -> VfsResult<usize> {
let mut next = self.next_request_id.lock();
let mut requests = self.requests.lock();
let id = loop {
let id = *next;
*next = id.wrapping_add(1);
if id != 0 && !requests.contains_key(&id) {
break id;
}
};
requests.insert(id, Vec::new());
drop(requests);
drop(next);
let res = (arg as *mut u32).vm_write(id);
if res.is_err() {
self.requests.lock().remove(&id);
}
res?;
Ok(0)
}
fn read_request_tasks(req: &librga_abi::RgaUserRequest) -> VfsResult<Vec<librga_abi::RgaReq>> {
if req.task_num == 0 || req.task_num > RGA_TASK_NUM_MAX || req.task_ptr == 0 {
return Err(VfsError::InvalidInput);
}
let elem = core::mem::size_of::<librga_abi::RgaReq>();
let base = req.task_ptr as usize;
let mut tasks = Vec::with_capacity(req.task_num as usize);
for i in 0..req.task_num as usize {
let p = base + i * elem;
let t: librga_abi::RgaReq = unsafe {
(p as *const librga_abi::RgaReq)
.vm_read_uninit()?
.assume_init()
};
tasks.push(t);
}
Ok(tasks)
}
fn handle_request_config(&self, arg: usize) -> VfsResult<usize> {
let ureq: librga_abi::RgaUserRequest = unsafe {
(arg as *const librga_abi::RgaUserRequest)
.vm_read_uninit()?
.assume_init()
};
let tasks = Self::read_request_tasks(&ureq)?;
let mut requests = self.requests.lock();
let slot = requests.get_mut(&ureq.id).ok_or(VfsError::InvalidInput)?;
*slot = tasks;
Ok(0)
}
fn handle_request_submit(&self, arg: usize) -> VfsResult<usize> {
let ureq: librga_abi::RgaUserRequest = unsafe {
(arg as *const librga_abi::RgaUserRequest)
.vm_read_uninit()?
.assume_init()
};
if ureq.sync_mode == librga_abi::RGA_BLIT_ASYNC {
return Err(VfsError::Unsupported);
}
let inline = if ureq.task_num > 0 {
Some(Self::read_request_tasks(&ureq)?)
} else {
None
};
let staged = self
.requests
.lock()
.remove(&ureq.id)
.ok_or(VfsError::InvalidInput)?;
let tasks = inline.unwrap_or(staged);
for task in &tasks {
self.execute_blit(task)?;
}
Ok(0)
}
fn handle_request_cancel(&self, arg: usize) -> VfsResult<usize> {
let id: u32 = unsafe { (arg as *const u32).vm_read_uninit()?.assume_init() };
if self.requests.lock().remove(&id).is_none() {
return Err(VfsError::InvalidInput);
}
Ok(0)
}
}
impl FileLike for RgaFile {
fn read(&self, dst: &mut IoDst) -> AxResult<usize> {
self.base.read(dst)
}
fn write(&self, src: &mut IoSrc) -> AxResult<usize> {
self.base.write(src)
}
fn stat(&self) -> AxResult<Kstat> {
self.base.stat()
}
fn path(&self) -> Cow<'_, str> {
self.base.path()
}
fn ioctl(&self, cmd: u32, arg: usize) -> AxResult<usize> {
if arg == 0 {
return Err(VfsError::InvalidInput);
}
match cmd {
librga_abi::RGA_BLIT_SYNC => self.handle_blit_sync(arg),
librga_abi::RGA_BLIT_ASYNC => Err(VfsError::Unsupported),
librga_abi::RGA_GET_VERSION => {
let mut version = [0u8; 16];
version[..4].copy_from_slice(b"3.02");
(arg as *mut [u8; 16]).vm_write(version)?;
Ok(0)
}
librga_abi::RGA_IOC_GET_DRVIER_VERSION => self.handle_get_driver_version(arg),
librga_abi::RGA_IOC_GET_HW_VERSION => self.handle_get_hw_version(arg),
librga_abi::RGA_IOC_IMPORT_BUFFER => self.handle_import_buffer(arg),
librga_abi::RGA_IOC_RELEASE_BUFFER => self.handle_release_buffer(arg),
librga_abi::RGA_IOC_REQUEST_CREATE => self.handle_request_create(arg),
librga_abi::RGA_IOC_REQUEST_CONFIG => self.handle_request_config(arg),
librga_abi::RGA_IOC_REQUEST_SUBMIT => self.handle_request_submit(arg),
librga_abi::RGA_IOC_REQUEST_CANCEL => self.handle_request_cancel(arg),
_ => Err(VfsError::NotATty),
}
}
fn open_flags(&self) -> u32 {
self.base.open_flags()
}
fn nonblocking(&self) -> bool {
self.base.nonblocking()
}
fn set_nonblocking(&self, nonblocking: bool) -> AxResult {
self.base.set_nonblocking(nonblocking)
}
}
impl Pollable for RgaFile {
fn poll(&self) -> IoEvents {
IoEvents::IN | IoEvents::OUT
}
fn register(&self, _context: &mut Context<'_>, _events: IoEvents) {}
}