1use super::{memory::DeviceUse, *};
3use std::{
4 fs::File,
5 os::fd::{AsRawFd, FromRawFd, OwnedFd},
6 sync::atomic::{AtomicU32, Ordering},
7 time::{Duration, Instant},
8};
9#[allow(
10 non_camel_case_types,
11 non_snake_case,
12 non_upper_case_globals,
13 dead_code,
14 missing_docs,
15 clippy::all
16)]
17#[path = "storage_ffi.rs"]
18mod ffi;
19use ffi::*;
20
21const REGISTER_BUFFERS: u32 = 0;
23const REGISTER_FILES: u32 = 2;
24const REGISTER_RESTRICTIONS: u32 = 11;
25const ENABLE_RINGS: u32 = 12;
26const REGISTER_EVENTFD: u32 = 4;
27const SINGLE_ISSUER: u32 = 1 << 12;
28const DEFER_TASKRUN: u32 = 1 << 13;
29const TASKRUN_FLAG: u32 = 1 << 9;
30const SQ_TASKRUN: u32 = 4;
31
32#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
34pub enum StorageProgress {
35 #[default]
37 Sqpoll,
38 Wait,
40}
41
42#[derive(Clone, Debug)]
44pub struct StorageOptions {
45 pub progress: StorageProgress,
47 pub entries: u32,
49 pub idle_milliseconds: u32,
51 pub memory_budget: Option<crate::residency::MemoryBudget>,
53}
54impl Default for StorageOptions {
55 fn default() -> Self {
56 Self {
57 progress: StorageProgress::Sqpoll,
58 entries: 8,
59 idle_milliseconds: 1,
60 memory_budget: None,
61 }
62 }
63}
64#[derive(Clone, Copy, Debug)]
67pub struct StorageLayout {
68 pub submission_entries: usize,
70 pub submission_tail: usize,
72 pub completion_entries: usize,
74 pub completion_head: usize,
76 pub completion_tail: usize,
78 pub submission_mask: u32,
80 pub completion_mask: u32,
82 pub host_payload: u64,
84}
85pub struct StorageRing {
89 fd: OwnedFd,
90 notification: Option<OwnedFd>,
91 issuer: libc::pid_t,
92 service_calls: AtomicU32,
93 wait_calls: AtomicU32,
94 memory: Buffer,
95 _kernel_use: DeviceUse,
98 payload: Buffer,
99 _files: Vec<File>,
100 parameters: io_uring_params,
101 control: usize,
102 layout: StorageLayout,
103}
104fn syscall_result(result: libc::c_long) -> Result<libc::c_long> {
105 if result < 0 {
106 Err(std::io::Error::last_os_error().into())
107 } else {
108 Ok(result)
109 }
110}
111impl StorageRing {
112 pub fn new(
117 device: &Device,
118 files: &[File],
119 payload: &Buffer,
120 options: StorageOptions,
121 ) -> Result<Self> {
122 if device.endpoint().engine() != Engine::Gpu
123 || files.is_empty()
124 || files.len() > 1024
125 || !options.entries.is_power_of_two()
126 || !(2..=4096).contains(&options.entries)
127 || options.idle_milliseconds == 0
128 {
129 return Err(Error::Message(
130 "invalid GPU storage ring configuration".into(),
131 ));
132 }
133 if !payload.owns_registered_pages() {
134 return Err(Error::Unsupported(
135 "storage payload requires owned registered host pages".into(),
136 ));
137 }
138 payload.device_address(device)?;
139 let files = files
140 .iter()
141 .map(|file| {
142 if !file.metadata()?.is_file() {
143 return Err(Error::Unsupported("storage requires regular files".into()));
144 }
145 Ok(file.try_clone()?)
146 })
147 .collect::<Result<Vec<_>>>()?;
148 let page = unsafe { libc::sysconf(libc::_SC_PAGESIZE) };
149 if page <= 0 {
150 return Err(Error::Unsupported("host page size unavailable".into()));
151 }
152 let page = page as usize;
153 let round = |n: usize| n.div_ceil(page) * page;
154 let control = round(options.entries as usize * 64);
155 let control_bytes = page + round(options.entries as usize * 2 * 16);
156 let memory = device.fabric().allocate_registered(
157 control + control_bytes,
158 std::slice::from_ref(device),
159 options.memory_budget.as_ref(),
160 )?;
161 let mut parameters = io_uring_params {
162 flags: IORING_SETUP_NO_MMAP
163 | IORING_SETUP_NO_SQARRAY
164 | IORING_SETUP_R_DISABLED
165 | match options.progress {
166 StorageProgress::Sqpoll => IORING_SETUP_SQPOLL,
167 StorageProgress::Wait => SINGLE_ISSUER | DEFER_TASKRUN | TASKRUN_FLAG,
168 },
169 sq_thread_idle: options.idle_milliseconds,
170 ..Default::default()
171 };
172 parameters.sq_off.user_addr = memory.host_pointer() as u64;
173 parameters.cq_off.user_addr = parameters.sq_off.user_addr + control as u64;
174 let fd =
175 unsafe { libc::syscall(libc::SYS_io_uring_setup, options.entries, &mut parameters) };
176 if fd < 0 {
177 let error = std::io::Error::last_os_error();
178 return if matches!(
179 error.raw_os_error(),
180 Some(libc::ENOSYS | libc::EINVAL | libc::EPERM | libc::EACCES)
181 ) {
182 Err(Error::Unsupported(format!(
183 "caller-owned io_uring ring unavailable: {error}"
184 )))
185 } else {
186 Err(error.into())
187 };
188 }
189 let fd = unsafe { OwnedFd::from_raw_fd(fd as i32) };
190 if parameters.sq_entries != options.entries
191 || parameters.cq_entries != options.entries * 2
192 || parameters.cq_off.cqes as usize + parameters.cq_entries as usize * 16 > control_bytes
193 {
194 return Err(Error::Unsupported(
195 "unexpected io_uring storage requirements".into(),
196 ));
197 }
198 for offset in [
199 parameters.sq_off.head,
200 parameters.sq_off.tail,
201 parameters.sq_off.flags,
202 parameters.sq_off.dropped,
203 parameters.cq_off.head,
204 parameters.cq_off.tail,
205 parameters.cq_off.overflow,
206 ] {
207 if offset % 4 != 0 || offset as usize + 4 > control_bytes {
208 return Err(Error::Unsupported("invalid native ring offsets".into()));
209 }
210 }
211 let layout = StorageLayout {
212 submission_entries: 0,
213 submission_tail: control + parameters.sq_off.tail as usize,
214 completion_entries: control + parameters.cq_off.cqes as usize,
215 completion_head: control + parameters.cq_off.head as usize,
216 completion_tail: control + parameters.cq_off.tail as usize,
217 submission_mask: parameters.sq_entries - 1,
218 completion_mask: parameters.cq_entries - 1,
219 host_payload: payload.host_pointer() as u64,
220 };
221 let kernel_use = memory.retain_use()?;
222 let notification = if options.progress == StorageProgress::Wait {
223 let fd = unsafe { libc::eventfd(0, libc::EFD_CLOEXEC | libc::EFD_NONBLOCK) };
224 syscall_result(fd as _)?;
225 Some(unsafe { OwnedFd::from_raw_fd(fd) })
226 } else {
227 None
228 };
229 let ring = Self {
230 fd,
231 notification,
232 issuer: unsafe { libc::syscall(libc::SYS_gettid) as libc::pid_t },
233 service_calls: AtomicU32::new(0),
234 wait_calls: AtomicU32::new(0),
235 memory,
236 _kernel_use: kernel_use,
237 payload: payload.clone(),
238 _files: files,
239 parameters,
240 control,
241 layout,
242 };
243 let iov = libc::iovec {
244 iov_base: payload.host_pointer().cast(),
245 iov_len: payload.len(),
246 };
247 ring.register(REGISTER_BUFFERS, &iov as *const _ as *const libc::c_void, 1)?;
248 let descriptors: Vec<i32> = ring._files.iter().map(AsRawFd::as_raw_fd).collect();
249 ring.register(
250 REGISTER_FILES,
251 descriptors.as_ptr().cast(),
252 descriptors.len() as u32,
253 )?;
254 if let Some(notification) = &ring.notification {
255 let fd = notification.as_raw_fd();
256 ring.register(REGISTER_EVENTFD, (&fd as *const i32).cast(), 1)?;
257 }
258 let restrictions =
260 [(1, 4), (1, 5), (2, 1), (3, 1), (0, ENABLE_RINGS as u8)].map(|(opcode, value)| {
261 io_uring_restriction {
262 opcode,
263 __bindgen_anon_1: io_uring_restriction__bindgen_ty_1 { register_op: value },
264 ..Default::default()
265 }
266 });
267 ring.register(
268 REGISTER_RESTRICTIONS,
269 restrictions.as_ptr().cast(),
270 restrictions.len() as u32,
271 )?;
272 ring.register(ENABLE_RINGS, ptr::null(), 0)?;
273 Ok(ring)
274 }
275 fn register(&self, operation: u32, data: *const libc::c_void, count: u32) -> Result<()> {
276 syscall_result(unsafe {
277 libc::syscall(
278 libc::SYS_io_uring_register,
279 self.fd.as_raw_fd(),
280 operation,
281 data,
282 count,
283 )
284 })?;
285 Ok(())
286 }
287 pub fn memory(&self) -> &Buffer {
290 &self.memory
291 }
292 pub fn payload(&self) -> &Buffer {
294 &self.payload
295 }
296 pub fn layout(&self) -> StorageLayout {
298 self.layout
299 }
300 fn word(&self, offset: u32) -> u32 {
301 unsafe {
302 (&*self
303 .memory
304 .host_pointer()
305 .add(self.control + offset as usize)
306 .cast::<AtomicU32>())
307 .load(Ordering::Acquire)
308 }
309 }
310 fn wake(&self) -> Result<()> {
311 if let Some(notification) = &self.notification {
312 if unsafe { libc::syscall(libc::SYS_gettid) as libc::pid_t } != self.issuer {
313 return Err(Error::Message(
314 "wait-mode storage must be serviced by its creating thread".into(),
315 ));
316 }
317 let mut value = 0u64;
318 let read =
319 unsafe { libc::read(notification.as_raw_fd(), (&mut value as *mut u64).cast(), 8) };
320 if read < 0 {
321 let error = std::io::Error::last_os_error();
322 if !matches!(error.raw_os_error(), Some(libc::EAGAIN | libc::EINTR)) {
323 return Err(error.into());
324 }
325 }
326 let completed = self.word(self.parameters.cq_off.tail);
327 let tail = self.word(self.parameters.sq_off.tail);
328 let head = self.word(self.parameters.sq_off.head);
329 if tail == completed {
330 return Ok(());
331 }
332 self.service_calls.fetch_add(1, Ordering::Relaxed);
333 let result = unsafe {
334 libc::syscall(
335 libc::SYS_io_uring_enter,
336 self.fd.as_raw_fd(),
337 tail.wrapping_sub(head),
338 0u32,
339 1u32,
340 ptr::null::<u8>(),
341 0usize,
342 )
343 };
344 if result < 0 && std::io::Error::last_os_error().raw_os_error() == Some(libc::EINTR) {
345 return Ok(());
346 }
347 syscall_result(result)?;
348 if self.word(self.parameters.cq_off.tail) == completed
349 && self.word(self.parameters.sq_off.flags) & SQ_TASKRUN == 0
350 && self.word(self.parameters.sq_off.head) != completed
351 {
352 let mut fd = libc::pollfd {
353 fd: notification.as_raw_fd(),
354 events: libc::POLLIN,
355 revents: 0,
356 };
357 self.wait_calls.fetch_add(1, Ordering::Relaxed);
358 let result = unsafe { libc::poll(&mut fd, 1, 1) };
360 if result < 0 && std::io::Error::last_os_error().raw_os_error() != Some(libc::EINTR)
361 {
362 syscall_result(result as _)?;
363 }
364 }
365 return Ok(());
366 }
367 if self.word(self.parameters.sq_off.flags) & IORING_SQ_NEED_WAKEUP != 0
368 && self.word(self.parameters.sq_off.tail) != self.word(self.parameters.sq_off.head)
369 {
370 self.service_calls.fetch_add(1, Ordering::Relaxed);
371 let result = unsafe {
372 libc::syscall(
373 libc::SYS_io_uring_enter,
374 self.fd.as_raw_fd(),
375 0u32,
376 0u32,
377 IORING_ENTER_SQ_WAKEUP,
378 ptr::null::<u8>(),
379 0usize,
380 )
381 };
382 if result < 0 && std::io::Error::last_os_error().raw_os_error() == Some(libc::EINTR) {
383 return Ok(());
384 }
385 syscall_result(result)?;
386 }
387 Ok(())
388 }
389 pub(crate) fn service_counts(&self) -> (u32, u32) {
390 (
391 self.service_calls.load(Ordering::Relaxed),
392 self.wait_calls.load(Ordering::Relaxed),
393 )
394 }
395 fn drained(&self) -> bool {
396 let tail = self.word(self.parameters.sq_off.tail);
397 tail == self.word(self.parameters.sq_off.head)
398 && tail == self.word(self.parameters.cq_off.tail)
399 }
400 pub unsafe fn dispatch(self, command: PreparedGpu) -> Result<StorageExecution> {
407 let uses = vec![self.memory.retain_use()?, self.payload.retain_use()?];
408 let done = unsafe { command.dispatch() }?;
409 Ok(StorageExecution {
410 owners: Some(StorageOwners {
411 ring: self,
412 _command: command,
413 done,
414 uses,
415 }),
416 retired: false,
417 })
418 }
419}
420struct StorageOwners {
421 ring: StorageRing,
422 _command: PreparedGpu,
423 done: Completion,
424 uses: Vec<DeviceUse>,
425}
426pub struct StorageExecution {
429 owners: Option<StorageOwners>,
430 retired: bool,
431}
432impl StorageExecution {
433 pub(crate) fn into_ring(mut self) -> Result<StorageRing> {
434 if !self.retired {
435 return Err(Error::Busy("storage execution has not retired".into()));
436 }
437 Ok(self.owners.take().unwrap().ring)
438 }
439 pub fn is_complete(&self) -> bool {
441 self.retired
442 }
443 pub fn wait_timeout(&mut self, timeout: Duration) -> Result<bool> {
446 if self.retired {
447 return Ok(true);
448 }
449 let start = Instant::now();
450 let owners = self.owners.as_mut().unwrap();
451 loop {
452 owners.ring.wake()?;
453 let gpu = owners.done.refresh()?;
454 if gpu && owners.ring.drained() {
455 self.retired = true;
456 owners.uses.clear();
457 return Ok(true);
458 }
459 if start.elapsed() >= timeout {
460 return Ok(false);
461 }
462 std::thread::yield_now();
463 }
464 }
465}
466impl Drop for StorageExecution {
467 fn drop(&mut self) {
468 if self.owners.is_some() && !self.wait_timeout(Duration::from_secs(10)).unwrap_or(false) {
469 std::mem::forget(self.owners.take());
470 }
471 }
472}