use core::ffi::{c_int, c_void};
use core::ptr::NonNull;
use libloading::Library;
use super::mapped::{Line, LineDriver, LineError};
use crate::core::line::{CompletionLine, Slot};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct HipStream(usize);
impl HipStream {
#[must_use]
pub const unsafe fn from_raw(raw: usize) -> Self {
Self(raw)
}
#[must_use]
pub const fn raw(self) -> usize {
self.0
}
}
const MAPPED: u32 = 0x2;
const COHERENT: u32 = 0x4000_0000;
type HostMalloc = unsafe extern "C" fn(*mut *mut c_void, usize, u32) -> i32;
type HostDevPtr = unsafe extern "C" fn(*mut *mut c_void, *mut c_void, u32) -> i32;
type HostFree = unsafe extern "C" fn(*mut c_void) -> i32;
type GetDevice = unsafe extern "C" fn(*mut c_int) -> i32;
type SetDevice = unsafe extern "C" fn(c_int) -> i32;
type DevSync = unsafe extern "C" fn() -> i32;
type StreamDevice = unsafe extern "C" fn(*mut c_void, *mut c_int) -> i32;
type PrimaryRetain = unsafe extern "C" fn(*mut *mut c_void, c_int) -> i32;
type PrimaryRelease = unsafe extern "C" fn(c_int) -> i32;
type WriteValue64 = unsafe extern "C" fn(*mut c_void, *mut c_void, u64, u32) -> i32;
pub(crate) struct HipDriver {
malloc: HostMalloc,
dev_ptr: HostDevPtr,
free: HostFree,
get_device: GetDevice,
set_device: SetDevice,
dev_sync: DevSync,
stream_device: StreamDevice,
retain: PrimaryRetain,
release: PrimaryRelease,
write: WriteValue64,
_lib: Library,
}
macro_rules! sym {
($lib:ident, $ty:ty, $name:literal) => {
*unsafe { $lib.get::<$ty>($name) }.ok()?
};
}
impl HipDriver {
pub(crate) fn resolve() -> Option<Self> {
let lib = unsafe { Library::new("libamdhip64.so") }.ok()?;
Some(Self {
malloc: sym!(lib, HostMalloc, b"hipHostMalloc"),
dev_ptr: sym!(lib, HostDevPtr, b"hipHostGetDevicePointer"),
free: sym!(lib, HostFree, b"hipHostFree"),
get_device: sym!(lib, GetDevice, b"hipGetDevice"),
set_device: sym!(lib, SetDevice, b"hipSetDevice"),
dev_sync: sym!(lib, DevSync, b"hipDeviceSynchronize"),
stream_device: sym!(lib, StreamDevice, b"hipStreamGetDevice"),
retain: sym!(lib, PrimaryRetain, b"hipDevicePrimaryCtxRetain"),
release: sym!(lib, PrimaryRelease, b"hipDevicePrimaryCtxRelease"),
write: sym!(lib, WriteValue64, b"hipStreamWriteValue64"),
_lib: lib,
})
}
}
fn ok(status: i32) -> Result<(), LineError> {
if status == 0 { Ok(()) } else { Err(LineError::Status(status)) }
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) struct HipDevice(c_int);
impl HipDriver {
fn on_device<T>(&self, device: HipDevice, f: impl FnOnce() -> T) -> Result<T, LineError> {
let mut prev: c_int = 0;
unsafe {
ok((self.get_device)(&raw mut prev))?;
ok((self.set_device)(device.0))?;
}
let r = f();
ok(unsafe { (self.set_device)(prev) })?;
Ok(r)
}
}
impl LineDriver for HipDriver {
type Stream = HipStream;
type Domain = HipDevice;
fn retain_current(&self) -> Result<HipDevice, LineError> {
let mut d: c_int = -1;
ok(unsafe { (self.get_device)(&raw mut d) })?;
if d < 0 {
return Err(LineError::NoDomain);
}
let mut ctx: *mut c_void = core::ptr::null_mut();
ok(unsafe { (self.retain)(&raw mut ctx, d) })?;
Ok(HipDevice(d))
}
fn release(&self, device: HipDevice) {
let _ = unsafe { (self.release)(device.0) };
}
fn alloc_host(&self, device: HipDevice, bytes: usize) -> Result<NonNull<c_void>, LineError> {
let mut page: *mut c_void = core::ptr::null_mut();
ok(self.on_device(device, || unsafe {
(self.malloc)(&raw mut page, bytes, MAPPED | COHERENT)
})?)?;
NonNull::new(page).ok_or(LineError::Unavailable)
}
fn device_address(&self, device: HipDevice, host: NonNull<c_void>) -> Result<u64, LineError> {
let mut addr: *mut c_void = core::ptr::null_mut();
ok(self.on_device(device, || unsafe {
(self.dev_ptr)(&raw mut addr, host.as_ptr(), 0)
})?)?;
Ok(addr as u64)
}
fn free_host(&self, _device: HipDevice, host: NonNull<c_void>) {
let _ = unsafe { (self.free)(host.as_ptr()) };
}
fn stream_in(&self, stream: HipStream, device: HipDevice) -> Result<bool, LineError> {
let mut d: c_int = -1;
ok(unsafe { (self.stream_device)(stream.0 as *mut c_void, &raw mut d) })?;
Ok(d == device.0)
}
fn write_value(&self, stream: HipStream, addr: u64, value: u64) -> Result<(), LineError> {
let ptr = usize::try_from(addr).map_err(|_| LineError::Unavailable)? as *mut c_void;
ok(unsafe { (self.write)(stream.0 as *mut c_void, ptr, value, 0) })
}
fn settle(&self, device: HipDevice) -> bool {
matches!(self.on_device(device, || unsafe { (self.dev_sync)() }), Ok(0))
}
}
pub struct MappedLine(Line<HipDriver>);
impl MappedLine {
pub fn new() -> Result<Self, LineError> {
Line::with_driver(HipDriver::resolve().ok_or(LineError::Unavailable)?).map(Self)
}
#[must_use]
pub fn line(&self) -> &CompletionLine {
self.0.line()
}
#[must_use]
pub fn device_address(&self) -> u64 {
self.0.device_address()
}
pub fn enqueue_store(&self, stream: HipStream, slot: Slot, value: u64) -> Result<(), LineError> {
self.0.enqueue_store(stream, slot, value)
}
}