blob: 506a018a101656613d09fe770775f80d2a360bd3 [file] [log] [blame]
// Copyright 2021 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//! C bindings for the rutabaga_gfx crate
extern crate rutabaga_gfx;
use std::cell::RefCell;
use std::convert::TryInto;
use std::ffi::CStr;
use std::ffi::CString;
#[cfg(goldfish)]
use std::io::IoSlice;
use std::io::IoSliceMut;
use std::os::raw::c_char;
use std::os::raw::c_void;
use std::panic::catch_unwind;
use std::panic::AssertUnwindSafe;
use std::path::Path;
use std::path::PathBuf;
use std::ptr::copy_nonoverlapping;
use std::ptr::null_mut;
use std::slice::from_raw_parts;
use std::slice::from_raw_parts_mut;
#[cfg(unix)]
use libc::iovec;
use libc::EINVAL;
use libc::ESRCH;
use rutabaga_gfx::ResourceCreate3D;
use rutabaga_gfx::ResourceCreateBlob;
use rutabaga_gfx::Rutabaga;
use rutabaga_gfx::RutabagaBuilder;
use rutabaga_gfx::RutabagaChannel;
use rutabaga_gfx::RutabagaComponentType;
use rutabaga_gfx::RutabagaDebug;
use rutabaga_gfx::RutabagaDebugHandler;
use rutabaga_gfx::RutabagaDescriptor;
use rutabaga_gfx::RutabagaFence;
use rutabaga_gfx::RutabagaFenceHandler;
use rutabaga_gfx::RutabagaFromRawDescriptor;
use rutabaga_gfx::RutabagaHandle;
use rutabaga_gfx::RutabagaImportData;
use rutabaga_gfx::RutabagaIntoRawDescriptor;
use rutabaga_gfx::RutabagaIovec;
use rutabaga_gfx::RutabagaRawDescriptor;
use rutabaga_gfx::RutabagaResult;
use rutabaga_gfx::RutabagaWsi;
use rutabaga_gfx::Transfer3D;
use rutabaga_gfx::RUTABAGA_DEBUG_ERROR;
#[cfg(not(unix))]
#[repr(C)]
pub struct iovec {
pub iov_base: *mut c_void,
pub iov_len: usize,
}
const NO_ERROR: i32 = 0;
const RUTABAGA_WSI_SURFACELESS: u64 = 1;
thread_local! {
static S_DEBUG_HANDLER: RefCell<Option<RutabagaDebugHandler>> = const { RefCell::new(None) };
}
fn log_error(debug_string: String) {
S_DEBUG_HANDLER.with(|handler_cell| {
if let Some(handler) = &*handler_cell.borrow() {
let cstring = CString::new(debug_string.as_str()).expect("CString creation failed");
let debug = RutabagaDebug {
debug_type: RUTABAGA_DEBUG_ERROR,
message: cstring.as_ptr(),
};
handler.call(debug);
}
});
}
fn return_result<T>(result: RutabagaResult<T>) -> i32 {
if let Err(e) = result {
log_error(e.to_string());
-EINVAL
} else {
NO_ERROR
}
}
macro_rules! return_on_error {
($result:expr) => {
match $result {
Ok(t) => t,
Err(e) => {
log_error(e.to_string());
return -EINVAL;
}
}
};
}
#[allow(non_camel_case_types)]
type rutabaga = Rutabaga;
#[allow(non_camel_case_types)]
type rutabaga_create_blob = ResourceCreateBlob;
#[allow(non_camel_case_types)]
type rutabaga_create_3d = ResourceCreate3D;
#[allow(non_camel_case_types)]
type rutabaga_transfer = Transfer3D;
#[allow(non_camel_case_types)]
type rutabaga_fence = RutabagaFence;
#[allow(non_camel_case_types)]
type rutabaga_debug = RutabagaDebug;
#[repr(C)]
#[derive(Copy, Clone)]
pub struct rutabaga_iovecs {
pub iovecs: *mut iovec,
pub num_iovecs: usize,
}
#[repr(C)]
#[derive(Copy, Clone)]
pub struct rutabaga_handle {
pub os_handle: i64,
pub handle_type: u32,
}
#[repr(C)]
pub struct rutabaga_mapping {
pub ptr: *mut c_void,
pub size: u64,
}
#[repr(C)]
pub struct rutabaga_channel {
pub channel_name: *const c_char,
pub channel_type: u32,
}
#[repr(C)]
pub struct rutabaga_channels {
pub channels: *const rutabaga_channel,
pub num_channels: usize,
}
#[repr(C)]
pub struct rutabaga_command {
pub ctx_id: u32,
pub cmd_size: u32,
pub cmd: *mut u8,
pub num_in_fences: u32,
pub fence_ids: *mut u64,
}
#[allow(non_camel_case_types)]
type rutabaga_import_data = RutabagaImportData;
#[allow(non_camel_case_types)]
pub type rutabaga_fence_callback = extern "C" fn(user_data: u64, fence: &rutabaga_fence);
#[allow(non_camel_case_types)]
pub type rutabaga_debug_callback = extern "C" fn(user_data: u64, debug: &rutabaga_debug);
#[repr(C)]
pub struct rutabaga_builder<'a> {
pub user_data: u64,
pub capset_mask: u64,
pub wsi: u64,
pub fence_cb: rutabaga_fence_callback,
pub debug_cb: Option<rutabaga_debug_callback>,
pub channels: Option<&'a rutabaga_channels>,
pub renderer_features: *const c_char,
}
fn create_ffi_fence_handler(
user_data: u64,
fence_cb: rutabaga_fence_callback,
) -> RutabagaFenceHandler {
RutabagaFenceHandler::new(move |completed_fence| fence_cb(user_data, &completed_fence))
}
fn create_ffi_debug_handler(
user_data: u64,
debug_cb: rutabaga_debug_callback,
) -> RutabagaDebugHandler {
RutabagaDebugHandler::new(move |rutabaga_debug| debug_cb(user_data, &rutabaga_debug))
}
#[no_mangle]
/// # Safety
/// - `capset_names` must be a null-terminated C-string.
pub unsafe extern "C" fn rutabaga_calculate_capset_mask(
capset_names: *const c_char,
capset_mask: &mut u64,
) -> i32 {
catch_unwind(AssertUnwindSafe(|| {
if capset_names.is_null() {
return -EINVAL;
}
let c_str_slice = CStr::from_ptr(capset_names);
let result = c_str_slice.to_str();
let str_slice = return_on_error!(result);
*capset_mask = rutabaga_gfx::calculate_capset_mask(str_slice.split(':'));
NO_ERROR
}))
.unwrap_or(-ESRCH)
}
/// # Safety
/// - If `(*builder).channels` is not null, the caller must ensure `(*channels).channels` points to
/// a valid array of `struct rutabaga_channel` of size `(*channels).num_channels`.
/// - The `channel_name` field of `struct rutabaga_channel` must be a null-terminated C-string.
#[no_mangle]
pub unsafe extern "C" fn rutabaga_init(builder: &rutabaga_builder, ptr: &mut *mut rutabaga) -> i32 {
catch_unwind(AssertUnwindSafe(|| {
let fence_handler = create_ffi_fence_handler(builder.user_data, builder.fence_cb);
let mut debug_handler_opt: Option<RutabagaDebugHandler> = None;
if let Some(func) = builder.debug_cb {
let debug_handler = create_ffi_debug_handler(builder.user_data, func);
S_DEBUG_HANDLER.with(|handler_cell| {
*handler_cell.borrow_mut() = Some(debug_handler.clone());
});
debug_handler_opt = Some(debug_handler);
}
let mut rutabaga_channels_opt = None;
if let Some(channels) = builder.channels {
let mut rutabaga_channels: Vec<RutabagaChannel> = Vec::new();
let channels_slice = from_raw_parts(channels.channels, channels.num_channels);
for channel in channels_slice {
let c_str_slice = CStr::from_ptr(channel.channel_name);
let result = c_str_slice.to_str();
let str_slice = return_on_error!(result);
let string = str_slice.to_owned();
let path = PathBuf::from(&string);
rutabaga_channels.push(RutabagaChannel {
base_channel: path,
channel_type: channel.channel_type,
});
}
rutabaga_channels_opt = Some(rutabaga_channels);
}
let mut renderer_features_opt = None;
let renderer_features_ptr = builder.renderer_features;
if !renderer_features_ptr.is_null() {
let c_str_slice = CStr::from_ptr(renderer_features_ptr);
let result = c_str_slice.to_str();
let str_slice = return_on_error!(result);
let string = str_slice.to_owned();
renderer_features_opt = Some(string);
}
let mut component_type = RutabagaComponentType::CrossDomain;
if builder.capset_mask == 0 {
component_type = RutabagaComponentType::Rutabaga2D;
}
let rutabaga_wsi = match builder.wsi {
RUTABAGA_WSI_SURFACELESS => RutabagaWsi::Surfaceless,
_ => return -EINVAL,
};
let result = RutabagaBuilder::new(component_type, builder.capset_mask)
.set_use_external_blob(false)
.set_use_egl(true)
.set_wsi(rutabaga_wsi)
.set_debug_handler(debug_handler_opt)
.set_rutabaga_channels(rutabaga_channels_opt)
.set_renderer_features(renderer_features_opt)
.build(fence_handler, None);
let rtbg = return_on_error!(result);
*ptr = Box::into_raw(Box::new(rtbg)) as _;
NO_ERROR
}))
.unwrap_or(-ESRCH)
}
/// # Safety
/// - `ptr` must have been created by `rutabaga_init`.
#[no_mangle]
pub extern "C" fn rutabaga_finish(ptr: &mut *mut rutabaga) -> i32 {
catch_unwind(AssertUnwindSafe(|| {
let _ = unsafe { Box::from_raw(*ptr) };
*ptr = null_mut();
NO_ERROR
}))
.unwrap_or(-ESRCH)
}
#[no_mangle]
pub extern "C" fn rutabaga_get_num_capsets(ptr: &mut rutabaga, num_capsets: &mut u32) -> i32 {
catch_unwind(AssertUnwindSafe(|| {
*num_capsets = ptr.get_num_capsets();
NO_ERROR
}))
.unwrap_or(-ESRCH)
}
#[no_mangle]
pub extern "C" fn rutabaga_get_capset_info(
ptr: &mut rutabaga,
capset_index: u32,
capset_id: &mut u32,
capset_version: &mut u32,
capset_size: &mut u32,
) -> i32 {
catch_unwind(AssertUnwindSafe(|| {
let result = ptr.get_capset_info(capset_index);
let info = return_on_error!(result);
*capset_id = info.0;
*capset_version = info.1;
*capset_size = info.2;
NO_ERROR
}))
.unwrap_or(-ESRCH)
}
/// # Safety
/// - `capset` must point an array of bytes of size `capset_size`.
#[no_mangle]
pub unsafe extern "C" fn rutabaga_get_capset(
ptr: &mut rutabaga,
capset_id: u32,
version: u32,
capset: *mut u8,
capset_size: u32,
) -> i32 {
catch_unwind(AssertUnwindSafe(|| {
let size: usize = capset_size.try_into().map_err(|_e| -EINVAL).unwrap();
let result = ptr.get_capset(capset_id, version);
let vec = return_on_error!(result);
copy_nonoverlapping(vec.as_ptr(), capset, size);
NO_ERROR
}))
.unwrap_or(-ESRCH)
}
#[no_mangle]
pub extern "C" fn rutabaga_context_create(
ptr: &mut rutabaga,
ctx_id: u32,
context_init: u32,
context_name: *const c_char,
context_name_len: u32,
) -> i32 {
let mut name: Option<&str> = None;
if !context_name.is_null() && context_name_len > 0 {
// Safe because context_name is not NULL and len is a positive integer, so the caller
// is expected to provide a valid pointer to an array of bytes at least as long as the
// passed length. If the provided byte array doesn't contain valid utf-8, name will be
// None.
let view = unsafe {
std::slice::from_raw_parts(context_name as *const u8, context_name_len as usize)
};
name = std::str::from_utf8(view).ok();
}
catch_unwind(AssertUnwindSafe(|| {
let result = ptr.create_context(ctx_id, context_init, name);
return_result(result)
}))
.unwrap_or(-ESRCH)
}
#[no_mangle]
pub extern "C" fn rutabaga_context_destroy(ptr: &mut rutabaga, ctx_id: u32) -> i32 {
catch_unwind(AssertUnwindSafe(|| {
let result = ptr.destroy_context(ctx_id);
return_result(result)
}))
.unwrap_or(-ESRCH)
}
#[no_mangle]
pub extern "C" fn rutabaga_context_attach_resource(
ptr: &mut rutabaga,
ctx_id: u32,
resource_id: u32,
) -> i32 {
catch_unwind(AssertUnwindSafe(|| {
let result = ptr.context_attach_resource(ctx_id, resource_id);
return_result(result)
}))
.unwrap_or(-ESRCH)
}
#[no_mangle]
pub extern "C" fn rutabaga_context_detach_resource(
ptr: &mut rutabaga,
ctx_id: u32,
resource_id: u32,
) -> i32 {
catch_unwind(AssertUnwindSafe(|| {
let result = ptr.context_detach_resource(ctx_id, resource_id);
return_result(result)
}))
.unwrap_or(-ESRCH)
}
#[no_mangle]
pub extern "C" fn rutabaga_resource_create_3d(
ptr: &mut rutabaga,
resource_id: u32,
create_3d: &rutabaga_create_3d,
) -> i32 {
catch_unwind(AssertUnwindSafe(|| {
let result = ptr.resource_create_3d(resource_id, *create_3d);
return_result(result)
}))
.unwrap_or(-ESRCH)
}
#[no_mangle]
pub unsafe extern "C" fn rutabaga_resource_import(
ptr: &mut rutabaga,
resource_id: u32,
import_handle: &rutabaga_handle,
import_data: &rutabaga_import_data,
) -> i32 {
catch_unwind(AssertUnwindSafe(|| {
let internal_handle = RutabagaHandle {
os_handle: RutabagaDescriptor::from_raw_descriptor(
import_handle.os_handle as RutabagaRawDescriptor,
),
handle_type: import_handle.handle_type,
};
let result = ptr.resource_import(resource_id, internal_handle, *import_data);
return_result(result)
}))
.unwrap_or(-ESRCH)
}
/// # Safety
/// - If `iovecs` is not null, the caller must ensure `(*iovecs).iovecs` points to a valid array of
/// iovecs of size `(*iovecs).num_iovecs`.
/// - Each iovec must point to valid memory starting at `iov_base` with length `iov_len`.
/// - Each iovec must valid until the resource's backing is explictly detached or the resource is is
/// unreferenced.
#[no_mangle]
pub unsafe extern "C" fn rutabaga_resource_attach_backing(
ptr: &mut rutabaga,
resource_id: u32,
iovecs: &rutabaga_iovecs,
) -> i32 {
catch_unwind(AssertUnwindSafe(|| {
let slice = from_raw_parts(iovecs.iovecs, iovecs.num_iovecs);
let vecs = slice
.iter()
.map(|iov| RutabagaIovec {
base: iov.iov_base,
len: iov.iov_len,
})
.collect();
let result = ptr.attach_backing(resource_id, vecs);
return_result(result)
}))
.unwrap_or(-ESRCH)
}
#[no_mangle]
pub extern "C" fn rutabaga_resource_detach_backing(ptr: &mut rutabaga, resource_id: u32) -> i32 {
catch_unwind(AssertUnwindSafe(|| {
let result = ptr.detach_backing(resource_id);
return_result(result)
}))
.unwrap_or(-ESRCH)
}
/// # Safety
/// - If `iovecs` is not null, the caller must ensure `(*iovecs).iovecs` points to a valid array of
/// iovecs of size `(*iovecs).num_iovecs`.
#[no_mangle]
pub unsafe extern "C" fn rutabaga_resource_transfer_read(
ptr: &mut rutabaga,
ctx_id: u32,
resource_id: u32,
transfer: &rutabaga_transfer,
buf: Option<&iovec>,
) -> i32 {
catch_unwind(AssertUnwindSafe(|| {
let mut slice_opt = None;
if let Some(iovec) = buf {
slice_opt = Some(IoSliceMut::new(std::slice::from_raw_parts_mut(
iovec.iov_base as *mut u8,
iovec.iov_len,
)));
}
let result = ptr.transfer_read(ctx_id, resource_id, *transfer, slice_opt);
return_result(result)
}))
.unwrap_or(-ESRCH)
}
#[no_mangle]
pub extern "C" fn rutabaga_resource_transfer_write(
ptr: &mut rutabaga,
ctx_id: u32,
resource_id: u32,
transfer: &rutabaga_transfer,
) -> i32 {
catch_unwind(AssertUnwindSafe(|| {
let result = ptr.transfer_write(ctx_id, resource_id, *transfer, None);
return_result(result)
}))
.unwrap_or(-ESRCH)
}
#[cfg(goldfish)]
#[no_mangle]
pub unsafe extern "C" fn rutabaga_resource_transfer_write_goldfish(
ptr: &mut rutabaga,
ctx_id: u32,
resource_id: u32,
transfer: &rutabaga_transfer,
buf: Option<&iovec>,
) -> i32 {
catch_unwind(AssertUnwindSafe(|| {
let slice = match buf {
Some(iov) => Some(IoSlice::new(std::slice::from_raw_parts(
iov.iov_base as *mut u8,
iov.iov_len,
))),
None => None,
};
let result = ptr.transfer_write(ctx_id, resource_id, *transfer, slice);
return_result(result)
}))
.unwrap_or(-ESRCH)
}
/// # Safety
/// - If `iovecs` is not null, the caller must ensure `(*iovecs).iovecs` points to a valid array of
/// iovecs of size `(*iovecs).num_iovecs`.
/// - If `handle` is not null, the caller must ensure it is a valid OS-descriptor. Ownership is
/// transfered to rutabaga.
/// - Each iovec must valid until the resource's backing is explictly detached or the resource is is
/// unreferenced.
#[no_mangle]
pub unsafe extern "C" fn rutabaga_resource_create_blob(
ptr: &mut rutabaga,
ctx_id: u32,
resource_id: u32,
create_blob: &rutabaga_create_blob,
iovecs: Option<&rutabaga_iovecs>,
handle: Option<&rutabaga_handle>,
) -> i32 {
catch_unwind(AssertUnwindSafe(|| {
let mut iovecs_opt: Option<Vec<RutabagaIovec>> = None;
if let Some(iovs) = iovecs {
let slice = if iovs.num_iovecs != 0 {
from_raw_parts(iovs.iovecs, iovs.num_iovecs)
} else {
&[]
};
let vecs = slice
.iter()
.map(|iov| RutabagaIovec {
base: iov.iov_base,
len: iov.iov_len,
})
.collect();
iovecs_opt = Some(vecs);
}
let mut handle_opt: Option<RutabagaHandle> = None;
// Only needed on Unix, since there is no way to create a handle from guest memory on
// Windows.
if let Some(hnd) = handle {
handle_opt = Some(RutabagaHandle {
os_handle: RutabagaDescriptor::from_raw_descriptor(
hnd.os_handle as RutabagaRawDescriptor,
),
handle_type: hnd.handle_type,
});
}
let result =
ptr.resource_create_blob(ctx_id, resource_id, *create_blob, iovecs_opt, handle_opt);
return_result(result)
}))
.unwrap_or(-ESRCH)
}
#[no_mangle]
pub extern "C" fn rutabaga_resource_unref(ptr: &mut rutabaga, resource_id: u32) -> i32 {
catch_unwind(AssertUnwindSafe(|| {
let result = ptr.unref_resource(resource_id);
return_result(result)
}))
.unwrap_or(-ESRCH)
}
/// # Safety
/// Caller owns raw descriptor on success and is responsible for closing it.
#[no_mangle]
pub extern "C" fn rutabaga_resource_export_blob(
ptr: &mut rutabaga,
resource_id: u32,
handle: &mut rutabaga_handle,
) -> i32 {
catch_unwind(AssertUnwindSafe(|| {
let result = ptr.export_blob(resource_id);
let hnd = return_on_error!(result);
handle.handle_type = hnd.handle_type;
handle.os_handle = hnd.os_handle.into_raw_descriptor() as i64;
NO_ERROR
}))
.unwrap_or(-ESRCH)
}
#[no_mangle]
pub extern "C" fn rutabaga_resource_map(
ptr: &mut rutabaga,
resource_id: u32,
mapping: &mut rutabaga_mapping,
) -> i32 {
catch_unwind(AssertUnwindSafe(|| {
let result = ptr.map(resource_id);
let internal_map = return_on_error!(result);
mapping.ptr = internal_map.ptr as *mut c_void;
mapping.size = internal_map.size;
NO_ERROR
}))
.unwrap_or(-ESRCH)
}
#[no_mangle]
pub extern "C" fn rutabaga_resource_unmap(ptr: &mut rutabaga, resource_id: u32) -> i32 {
catch_unwind(AssertUnwindSafe(|| {
let result = ptr.unmap(resource_id);
return_result(result)
}))
.unwrap_or(-ESRCH)
}
#[no_mangle]
pub extern "C" fn rutabaga_resource_map_info(
ptr: &mut rutabaga,
resource_id: u32,
map_info: &mut u32,
) -> i32 {
catch_unwind(AssertUnwindSafe(|| {
let result = ptr.map_info(resource_id);
*map_info = return_on_error!(result);
NO_ERROR
}))
.unwrap_or(-ESRCH)
}
/// # Safety
/// - `commands` must point to a contiguous memory region of `size` bytes.
#[no_mangle]
pub unsafe extern "C" fn rutabaga_submit_command(
ptr: &mut rutabaga,
cmd: &rutabaga_command,
) -> i32 {
catch_unwind(AssertUnwindSafe(|| {
let cmd_slice = if cmd.cmd_size != 0 {
from_raw_parts_mut(cmd.cmd, cmd.cmd_size as usize)
} else {
&mut []
};
let fence_ids = if cmd.num_in_fences != 0 {
from_raw_parts(cmd.fence_ids, cmd.num_in_fences as usize)
} else {
&mut []
};
let result = ptr.submit_command(cmd.ctx_id, cmd_slice, fence_ids);
return_result(result)
}))
.unwrap_or(-ESRCH)
}
#[no_mangle]
pub extern "C" fn rutabaga_create_fence(ptr: &mut rutabaga, fence: &rutabaga_fence) -> i32 {
catch_unwind(AssertUnwindSafe(|| {
let result = ptr.create_fence(*fence);
return_result(result)
}))
.unwrap_or(-ESRCH)
}
/// # Safety
/// - `dir` must be a null-terminated C-string.
#[no_mangle]
pub unsafe extern "C" fn rutabaga_snapshot(ptr: &mut rutabaga, dir: *const c_char) -> i32 {
catch_unwind(AssertUnwindSafe(|| {
let c_str_slice = CStr::from_ptr(dir);
let result = c_str_slice.to_str();
let directory = return_on_error!(result);
let result = ptr.snapshot(Path::new(directory));
return_result(result)
}))
.unwrap_or(-ESRCH)
}
/// # Safety
/// - `dir` must be a null-terminated C-string.
#[no_mangle]
pub unsafe extern "C" fn rutabaga_restore(ptr: &mut rutabaga, dir: *const c_char) -> i32 {
catch_unwind(AssertUnwindSafe(|| {
let c_str_slice = CStr::from_ptr(dir);
let result = c_str_slice.to_str();
let directory = return_on_error!(result);
let result = ptr.restore(Path::new(directory));
return_result(result)
}))
.unwrap_or(-ESRCH)
}