#![allow(unsafe_code)]
use core::cell::Cell;
use core::ffi::c_void;
use core::ptr::NonNull;
use core::sync::atomic::Ordering;
use crate::core::line::{CompletionLine, Slot};
pub(crate) const PAGE: usize = 4096;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum LineError {
Status(i32),
Unavailable,
NoDomain,
ForeignStream,
}
impl LineError {
#[must_use]
pub fn status(self) -> i32 {
match self {
Self::Status(s) => s,
Self::Unavailable => -1,
Self::NoDomain => -3,
Self::ForeignStream => -4,
}
}
}
impl core::fmt::Display for LineError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::Status(s) => write!(f, "driver status {s}"),
Self::Unavailable => f.write_str("the driver or one of its entry points is missing"),
Self::NoDomain => f.write_str("no current context on this thread"),
Self::ForeignStream => f.write_str("the stream belongs to another context"),
}
}
}
impl core::error::Error for LineError {}
impl From<LineError> for crate::Error {
fn from(e: LineError) -> Self {
crate::error!(Backend, "completion line", e.status())
}
}
pub(crate) trait LineDriver {
type Stream: Copy;
type Domain: Copy;
fn retain_current(&self) -> Result<Self::Domain, LineError>;
fn release(&self, domain: Self::Domain);
fn alloc_host(&self, domain: Self::Domain, bytes: usize) -> Result<NonNull<c_void>, LineError>;
fn device_address(&self, domain: Self::Domain, host: NonNull<c_void>) -> Result<u64, LineError>;
fn free_host(&self, domain: Self::Domain, host: NonNull<c_void>);
fn stream_in(&self, stream: Self::Stream, domain: Self::Domain) -> Result<bool, LineError>;
fn write_value(&self, stream: Self::Stream, addr: u64, value: u64) -> Result<(), LineError>;
fn settle(&self, domain: Self::Domain) -> bool;
}
pub(crate) struct Line<D: LineDriver> {
host: NonNull<CompletionLine>,
device: u64,
driver: D,
domain: D::Domain,
queued: Cell<bool>,
}
unsafe impl<D: LineDriver + Send> Send for Line<D> {}
impl<D: LineDriver> Line<D> {
pub(crate) fn with_driver(driver: D) -> Result<Self, LineError> {
let domain = driver.retain_current()?;
let page = match driver.alloc_host(domain, PAGE) {
Ok(page) => page,
Err(e) => {
driver.release(domain);
return Err(e);
}
};
let mut line = Self {
host: page.cast::<CompletionLine>(),
device: 0,
driver,
domain,
queued: Cell::new(false),
};
line.device = line.driver.device_address(domain, page)?;
for w in &line.line().words {
w.store(0, Ordering::Release);
}
Ok(line)
}
#[cfg(feature = "cuda")]
pub(crate) fn driver(&self) -> &D {
&self.driver
}
pub(crate) fn device_address(&self) -> u64 {
self.device
}
pub(crate) fn line(&self) -> &CompletionLine {
unsafe { self.host.as_ref() }
}
pub(crate) fn enqueue_store(
&self,
stream: D::Stream,
slot: Slot,
value: u64,
) -> Result<(), LineError> {
let offset = 8 * u64::try_from(slot.index()).map_err(|_| LineError::Unavailable)?;
if !self.driver.stream_in(stream, self.domain)? {
return Err(LineError::ForeignStream);
}
self.queued.set(true);
self.driver.write_value(stream, self.device + offset, value)
}
fn settled(&self) -> bool {
!self.queued.get() || self.driver.settle(self.domain)
}
}
impl<D: LineDriver> Drop for Line<D> {
fn drop(&mut self) {
if !self.settled() {
return;
}
self.driver.free_host(self.domain, self.host.cast::<c_void>());
self.driver.release(self.domain);
}
}
#[cfg(test)]
#[path = "mapped_tests.rs"]
mod tests;