Skip to main content

baracuda_cufile_sys/
lib.rs

1//! Raw FFI + dynamic loader for NVIDIA cuFile (GPUDirect Storage).
2//!
3//! Linux-only: cuFile calls `ibverbs` + NVIDIA's GDS kernel driver and has
4//! no Windows/macOS analogue. On non-Linux platforms the loader returns
5//! [`LoaderError::UnsupportedPlatform`].
6
7#![allow(non_camel_case_types, non_snake_case, non_upper_case_globals)]
8#![warn(missing_debug_implementations)]
9
10use core::ffi::{c_char, c_int, c_uint, c_void};
11use std::sync::OnceLock;
12
13use baracuda_core::{Library, LoaderError};
14use baracuda_types::CudaStatus;
15
16/// cuFile operation status (a.k.a. `CUfileOpError`).
17#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
18#[repr(transparent)]
19pub struct CUfileOpError(pub i32);
20
21impl CUfileOpError {
22    pub const SUCCESS: Self = Self(0);
23    pub const INTERNAL: Self = Self(5001);
24    pub const DRIVER_NOT_INITIALIZED: Self = Self(5002);
25    pub const IO_NOT_SUPPORTED: Self = Self(5003);
26    pub const NOT_REGISTERED: Self = Self(5004);
27    pub const INVALID_FILE_HANDLE: Self = Self(5005);
28
29    pub const fn is_success(self) -> bool {
30        self.0 == 0
31    }
32}
33
34impl CudaStatus for CUfileOpError {
35    fn code(self) -> i32 {
36        self.0
37    }
38    fn name(self) -> &'static str {
39        match self.0 {
40            0 => "CU_FILE_SUCCESS",
41            5001 => "CU_FILE_INTERNAL_ERROR",
42            5002 => "CU_FILE_DRIVER_NOT_INITIALIZED",
43            5003 => "CU_FILE_IO_NOT_SUPPORTED",
44            5004 => "CU_FILE_NOT_REGISTERED",
45            5005 => "CU_FILE_INVALID_FILE_HANDLE",
46            _ => "CU_FILE_UNRECOGNIZED",
47        }
48    }
49    fn description(self) -> &'static str {
50        match self.0 {
51            0 => "success",
52            5002 => "cuFile driver not initialized — call cuFileDriverOpen first",
53            5003 => "I/O not supported on this file / filesystem",
54            _ => "unrecognized cuFile status code",
55        }
56    }
57    fn is_success(self) -> bool {
58        CUfileOpError::is_success(self)
59    }
60    fn library(self) -> &'static str {
61        "cufile"
62    }
63}
64
65/// `CUfileError_t` — status plus a CUDA error code (for I/O that
66/// dispatched through the GPU).
67#[repr(C)]
68#[derive(Copy, Clone, Debug)]
69pub struct CUfileError_t {
70    pub err: CUfileOpError,
71    pub cu_err: c_int, // CUresult (driver-API error)
72}
73
74/// `CUfileHandle_t` — opaque handle returned by `cuFileHandleRegister`.
75pub type CUfileHandle_t = *mut c_void;
76
77/// `CUfileDescr_t` — struct describing a file (Unix fd or Win32 handle)
78/// for registration. cuFile's own layout is an outer tagged union; we
79/// model only the Unix-fd path since this API is Linux-only anyway.
80#[repr(C)]
81#[derive(Copy, Clone, Debug)]
82pub struct CUfileDescr_t {
83    /// Handle-type selector: 1 = CU_FILE_HANDLE_TYPE_OPAQUE_FD.
84    pub handle_type: c_int,
85    pub handle_fd: c_int,
86    /// 40-byte reserved tail; cuFile's real union is larger for Win32 +
87    /// opaque handles but Linux-only builds don't touch it.
88    pub _reserved: [u8; 40],
89    pub fs_ops: *mut c_void,
90}
91
92impl Default for CUfileDescr_t {
93    fn default() -> Self {
94        Self {
95            handle_type: 1, // OPAQUE_FD
96            handle_fd: -1,
97            _reserved: [0; 40],
98            fs_ops: core::ptr::null_mut(),
99        }
100    }
101}
102
103// ---- PFN types ----
104
105pub type PFN_cuFileDriverOpen = unsafe extern "C" fn() -> CUfileError_t;
106pub type PFN_cuFileDriverClose = unsafe extern "C" fn() -> CUfileError_t;
107pub type PFN_cuFileDriverGetProperties = unsafe extern "C" fn(props: *mut c_void) -> CUfileError_t;
108pub type PFN_cuFileDriverSetPollMode =
109    unsafe extern "C" fn(poll: bool, poll_threshold_size: usize) -> CUfileError_t;
110
111pub type PFN_cuFileHandleRegister =
112    unsafe extern "C" fn(fh: *mut CUfileHandle_t, descr: *mut CUfileDescr_t) -> CUfileError_t;
113pub type PFN_cuFileHandleDeregister = unsafe extern "C" fn(fh: CUfileHandle_t);
114
115pub type PFN_cuFileBufRegister =
116    unsafe extern "C" fn(buf_ptr: *mut c_void, length: usize, flags: c_int) -> CUfileError_t;
117pub type PFN_cuFileBufDeregister = unsafe extern "C" fn(buf_ptr: *mut c_void) -> CUfileError_t;
118
119pub type PFN_cuFileRead = unsafe extern "C" fn(
120    fh: CUfileHandle_t,
121    buf_ptr: *mut c_void,
122    size: usize,
123    file_offset: i64,
124    buf_ptr_offset: i64,
125) -> isize;
126pub type PFN_cuFileWrite = unsafe extern "C" fn(
127    fh: CUfileHandle_t,
128    buf_ptr: *const c_void,
129    size: usize,
130    file_offset: i64,
131    buf_ptr_offset: i64,
132) -> isize;
133
134pub type PFN_cuFileGetVersion = unsafe extern "C" fn(version: *mut c_int) -> CUfileError_t;
135
136pub type PFN_cuFileOpStatusError = unsafe extern "C" fn(status: CUfileOpError) -> *const c_char;
137
138// ---- Stream-registered (v1.6+) async APIs ----
139
140pub type PFN_cuFileReadAsync = unsafe extern "C" fn(
141    fh: CUfileHandle_t,
142    buf_ptr: *mut c_void,
143    size_p: *mut usize,
144    file_offset_p: *mut i64,
145    buf_ptr_offset_p: *mut i64,
146    bytes_read: *mut isize,
147    stream: *mut c_void,
148) -> CUfileError_t;
149
150pub type PFN_cuFileWriteAsync = unsafe extern "C" fn(
151    fh: CUfileHandle_t,
152    buf_ptr: *const c_void,
153    size_p: *mut usize,
154    file_offset_p: *mut i64,
155    buf_ptr_offset_p: *mut i64,
156    bytes_written: *mut isize,
157    stream: *mut c_void,
158) -> CUfileError_t;
159
160pub type PFN_cuFileStreamRegister =
161    unsafe extern "C" fn(stream: *mut c_void, flags: c_uint) -> CUfileError_t;
162
163pub type PFN_cuFileStreamDeregister = unsafe extern "C" fn(stream: *mut c_void) -> CUfileError_t;
164
165// ---- Batched I/O (v1.6+) ----
166
167/// Opcode selector for `CUfileIOParams_t`.
168#[allow(non_snake_case)]
169pub mod CUfileOpcode {
170    pub const READ: i32 = 0;
171    pub const WRITE: i32 = 1;
172}
173
174/// `CUfileIOParams_t` — 1 entry in a batched-IO request.
175///
176/// The real C struct is a tagged union — we expose the most-common
177/// "opaque FD + device memory" flavor via `fh`, `dev_ptr`, `file_offset`,
178/// `dev_ptr_offset`, `size`, and `opcode`. `mode` selector is always 0
179/// (BATCH, the only shipped flavor today).
180#[repr(C)]
181#[derive(Copy, Clone, Debug)]
182pub struct CUfileIOParams_t {
183    pub mode: c_int, // always 0 == CUFILE_BATCH
184    pub fh: CUfileHandle_t,
185    pub opcode: c_int,
186    pub cookie: *mut c_void,
187    pub dev_ptr_base: *mut c_void,
188    pub file_offset: i64,
189    pub dev_ptr_offset: i64,
190    pub size: usize,
191}
192
193impl Default for CUfileIOParams_t {
194    fn default() -> Self {
195        Self {
196            mode: 0,
197            fh: core::ptr::null_mut(),
198            opcode: 0,
199            cookie: core::ptr::null_mut(),
200            dev_ptr_base: core::ptr::null_mut(),
201            file_offset: 0,
202            dev_ptr_offset: 0,
203            size: 0,
204        }
205    }
206}
207
208/// `CUfileIOEvents_t` — status for a single batched-I/O entry.
209#[repr(C)]
210#[derive(Copy, Clone, Debug, Default)]
211pub struct CUfileIOEvents_t {
212    pub cookie: *mut c_void,
213    pub status: c_int,
214    pub ret: usize,
215}
216
217unsafe impl Send for CUfileIOEvents_t {}
218
219/// Opaque batch-handle (maps to `CUfileBatchHandle_t`).
220pub type CUfileBatchHandle_t = *mut c_void;
221
222pub type PFN_cuFileBatchIOSetUp = unsafe extern "C" fn(
223    batch_handle_out: *mut CUfileBatchHandle_t,
224    num_batches: c_uint,
225) -> CUfileError_t;
226
227pub type PFN_cuFileBatchIOSubmit = unsafe extern "C" fn(
228    batch_handle: CUfileBatchHandle_t,
229    num_entries: c_uint,
230    io_batch_params: *mut CUfileIOParams_t,
231    flags: c_uint,
232) -> CUfileError_t;
233
234pub type PFN_cuFileBatchIOGetStatus = unsafe extern "C" fn(
235    batch_handle: CUfileBatchHandle_t,
236    min_nr: c_uint,
237    nr: *mut c_uint,
238    io_batch_events: *mut CUfileIOEvents_t,
239    timeout: *mut c_void, // struct timespec* — opaque on Linux
240) -> CUfileError_t;
241
242pub type PFN_cuFileBatchIOCancel =
243    unsafe extern "C" fn(batch_handle: CUfileBatchHandle_t) -> CUfileError_t;
244
245pub type PFN_cuFileBatchIODestroy =
246    unsafe extern "C" fn(batch_handle: CUfileBatchHandle_t) -> CUfileError_t;
247
248// ---- Compatibility-mode helpers ----
249
250pub type PFN_cuFileUseCount = unsafe extern "C" fn(fh: CUfileHandle_t) -> c_int;
251
252// ---- Driver-tuning setters (v1.6+) ----
253
254pub type PFN_cuFileDriverSetMaxDirectIOSize =
255    unsafe extern "C" fn(max_direct_io_size_kb: usize) -> CUfileError_t;
256
257pub type PFN_cuFileDriverSetMaxCacheSize =
258    unsafe extern "C" fn(max_cache_size_kb: usize) -> CUfileError_t;
259
260pub type PFN_cuFileDriverSetMaxPinnedMemSize =
261    unsafe extern "C" fn(max_pinned_size_kb: usize) -> CUfileError_t;
262
263// ---- Loader ----
264
265macro_rules! cufile_fns {
266    ($($(#[$attr:meta])* fn $name:ident as $sym:literal : $pfn:ty;)*) => {
267        pub struct Cufile {
268            pub lib: Library,
269            $(
270                $name: OnceLock<$pfn>,
271            )*
272        }
273
274        impl core::fmt::Debug for Cufile {
275            fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
276                f.debug_struct("Cufile").field("lib", &self.lib).finish_non_exhaustive()
277            }
278        }
279
280        impl Cufile {
281            #[allow(dead_code)]
282            fn empty(lib: Library) -> Self {
283                Self { lib, $($name: OnceLock::new(),)* }
284            }
285            $(
286                $(#[$attr])*
287                #[doc = concat!("Resolve `", $sym, "`.")]
288                pub fn $name(&self) -> Result<$pfn, LoaderError> {
289                    if let Some(&p) = self.$name.get() { return Ok(p); }
290                    let raw: *mut () = unsafe { self.lib.raw_symbol($sym)? };
291                    let p: $pfn = unsafe { core::mem::transmute_copy::<*mut (), $pfn>(&raw) };
292                    let _ = self.$name.set(p);
293                    Ok(p)
294                }
295            )*
296        }
297    };
298}
299
300cufile_fns! {
301    fn cu_file_driver_open as "cuFileDriverOpen": PFN_cuFileDriverOpen;
302    fn cu_file_driver_close as "cuFileDriverClose": PFN_cuFileDriverClose;
303    fn cu_file_driver_get_properties as "cuFileDriverGetProperties":
304        PFN_cuFileDriverGetProperties;
305    fn cu_file_driver_set_poll_mode as "cuFileDriverSetPollMode":
306        PFN_cuFileDriverSetPollMode;
307    fn cu_file_handle_register as "cuFileHandleRegister": PFN_cuFileHandleRegister;
308    fn cu_file_handle_deregister as "cuFileHandleDeregister": PFN_cuFileHandleDeregister;
309    fn cu_file_buf_register as "cuFileBufRegister": PFN_cuFileBufRegister;
310    fn cu_file_buf_deregister as "cuFileBufDeregister": PFN_cuFileBufDeregister;
311    fn cu_file_read as "cuFileRead": PFN_cuFileRead;
312    fn cu_file_write as "cuFileWrite": PFN_cuFileWrite;
313    fn cu_file_get_version as "cuFileGetVersion": PFN_cuFileGetVersion;
314    fn cu_file_op_status_error as "cuFileGetOpStatusErrorString":
315        PFN_cuFileOpStatusError;
316
317    // Async I/O (stream-registered, v1.6+)
318    fn cu_file_read_async as "cuFileReadAsync": PFN_cuFileReadAsync;
319    fn cu_file_write_async as "cuFileWriteAsync": PFN_cuFileWriteAsync;
320    fn cu_file_stream_register as "cuFileStreamRegister": PFN_cuFileStreamRegister;
321    fn cu_file_stream_deregister as "cuFileStreamDeregister": PFN_cuFileStreamDeregister;
322
323    // Batched I/O (v1.6+)
324    fn cu_file_batch_io_set_up as "cuFileBatchIOSetUp": PFN_cuFileBatchIOSetUp;
325    fn cu_file_batch_io_submit as "cuFileBatchIOSubmit": PFN_cuFileBatchIOSubmit;
326    fn cu_file_batch_io_get_status as "cuFileBatchIOGetStatus": PFN_cuFileBatchIOGetStatus;
327    fn cu_file_batch_io_cancel as "cuFileBatchIOCancel": PFN_cuFileBatchIOCancel;
328    fn cu_file_batch_io_destroy as "cuFileBatchIODestroy": PFN_cuFileBatchIODestroy;
329
330    // Compat / diagnostic
331    fn cu_file_use_count as "cuFileUseCount": PFN_cuFileUseCount;
332
333    // Driver tuning
334    fn cu_file_driver_set_max_direct_io_size as "cuFileDriverSetMaxDirectIOSize":
335        PFN_cuFileDriverSetMaxDirectIOSize;
336    fn cu_file_driver_set_max_cache_size as "cuFileDriverSetMaxCacheSize":
337        PFN_cuFileDriverSetMaxCacheSize;
338    fn cu_file_driver_set_max_pinned_mem_size as "cuFileDriverSetMaxPinnedMemSize":
339        PFN_cuFileDriverSetMaxPinnedMemSize;
340}
341
342#[cfg(target_os = "linux")]
343fn cufile_candidates() -> &'static [&'static str] {
344    &["libcufile.so.0", "libcufile.so"]
345}
346
347pub fn cufile() -> Result<&'static Cufile, LoaderError> {
348    static CUFILE: OnceLock<Cufile> = OnceLock::new();
349    if let Some(c) = CUFILE.get() {
350        return Ok(c);
351    }
352    #[cfg(not(target_os = "linux"))]
353    {
354        Err(LoaderError::UnsupportedPlatform {
355            platform: std::env::consts::OS,
356        })
357    }
358    #[cfg(target_os = "linux")]
359    {
360        let lib = Library::open("cufile", cufile_candidates())?;
361        let _ = CUFILE.set(Cufile::empty(lib));
362        Ok(CUFILE.get().expect("OnceLock set or lost race"))
363    }
364}