1use core::{
8 num::NonZero,
9 ptr::NonNull,
10 slice,
11 sync::atomic::{AtomicUsize, Ordering},
12};
13use std::{
14 io,
15 os::fd::{AsRawFd, BorrowedFd, FromRawFd, OwnedFd, RawFd},
16};
17
18use fack::prelude::Error;
19
20use crate::ffi::binding;
21
22pub mod action;
23pub mod backend;
24
25#[derive(Debug, Clone, Copy, Error, PartialEq, Eq)]
27pub enum InvalidSlabSize {
28 #[error("exception slab size cannot be zero")]
30 Zero,
31
32 #[error("exception slab size is not aligned")]
34 Misaligned,
35
36 #[error("exception slab size exceeds the kernel limit")]
38 TooLarge,
39}
40
41#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
46pub struct SlabSize(
47 NonZero<usize>,
49);
50
51impl SlabSize {
52 pub const DEFAULT: Self = Self(
54 NonZero::new(binding::MIRILLA_EXCEPT_DEFAULT_SLAB_SIZE as usize)
55 .expect("the default slab size is nonzero"),
56 );
57
58 #[inline]
60 pub const fn new(value: usize) -> Result<Self, InvalidSlabSize> {
61 match NonZero::new(value) {
62 None => Err(InvalidSlabSize::Zero),
63 Some(target_size) => {
64 let size_value = target_size.get();
65 let page_aligned = size_value.is_multiple_of(4096);
66 let record_aligned = size_value
67 .is_multiple_of(core::mem::size_of::<binding::mirilla_except_record>());
68 let within_limit = size_value <= binding::MIRILLA_EXCEPT_SLAB_SIZE_LIMIT as usize;
69
70 match (page_aligned, record_aligned, within_limit) {
71 (true, true, true) => Ok(Self(target_size)),
72 (_, _, false) => Err(InvalidSlabSize::TooLarge),
73 _ => Err(InvalidSlabSize::Misaligned),
74 }
75 }
76 }
77 }
78
79 #[inline]
81 pub const fn get(self) -> usize {
82 let Self(value) = self;
83
84 value.get()
85 }
86
87 #[inline]
89 pub const fn record_capacity(self) -> usize {
90 let Self(target_size) = self;
91
92 target_size.get() / core::mem::size_of::<binding::mirilla_except_record>()
93 }
94}
95
96#[derive(Debug, Clone, Copy, Error, PartialEq, Eq)]
98pub enum InvalidSoftSlabLimit {
99 #[error("exception slab limit cannot be zero")]
101 Zero,
102
103 #[error("exception slab limit exceeds the kernel limit")]
105 AboveKernelLimit,
106}
107
108#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
113pub struct SoftSlabLimit(
114 NonZero<usize>,
116);
117
118impl SoftSlabLimit {
119 pub const DEFAULT: Self = Self(NonZero::<usize>::MIN);
121
122 #[inline]
124 pub const fn new(value: usize) -> Result<Self, InvalidSoftSlabLimit> {
125 match NonZero::new(value) {
126 None => Err(InvalidSoftSlabLimit::Zero),
127 Some(value) => match value.get() <= binding::MIRILLA_EXCEPT_SLAB_LIMIT as usize {
128 true => Ok(Self(value)),
129 false => Err(InvalidSoftSlabLimit::AboveKernelLimit),
130 },
131 }
132 }
133
134 #[inline]
136 pub const fn get(self) -> usize {
137 let Self(value) = self;
138
139 value.get()
140 }
141}
142
143#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
147pub struct ExceptionId(
148 NonZero<binding::mirilla_except_id_t>,
150);
151
152impl ExceptionId {
153 #[inline]
155 const fn from_raw(target_id: binding::mirilla_except_id_t) -> Option<Self> {
156 match NonZero::new(target_id) {
157 Some(target_id) => Some(Self(target_id)),
158 None => None,
159 }
160 }
161
162 #[inline]
164 pub const fn get(self) -> binding::mirilla_except_id_t {
165 let Self(value) = self;
166
167 value.get()
168 }
169}
170
171#[derive(Debug, Error)]
173pub enum SlabAllocationError {
174 #[error("exception slab allocation limit reached")]
176 SoftLimitReached,
177
178 #[error("exception slab mapping failed with {0}")]
180 #[error(source(0))]
181 System(
182 io::Error,
184 ),
185}
186
187#[derive(Debug)]
193pub struct Context(
194 OwnedFd,
196 ExceptionId,
198 SlabSize,
200 SoftSlabLimit,
202 AtomicUsize,
204);
205
206impl Context {
207 #[inline]
217 pub unsafe fn create(
218 device: BorrowedFd<'_>,
219 slab_size: SlabSize,
220 soft_limit: SoftSlabLimit,
221 ) -> io::Result<Self> {
222 let mut target_id = 0 as binding::mirilla_except_id_t;
223 let mut target_fd = -1 as RawFd;
224
225 let target_status = unsafe {
228 binding::catalejo_mirilla_except_create(
229 device.as_raw_fd(),
230 slab_size.get() as binding::virtual_size_t,
231 &raw mut target_id,
232 &raw mut target_fd,
233 )
234 };
235
236 status(target_status)?;
237
238 let target_id = ExceptionId::from_raw(target_id);
239 let target_fd = match target_fd {
240 0.. => {
241 Some(unsafe { OwnedFd::from_raw_fd(target_fd) })
243 }
244 _ => None,
245 };
246
247 match (target_id, target_fd) {
248 (Some(target_id), Some(target_fd)) => {
249 let allocated_count = AtomicUsize::new(0);
250
251 Ok(Self(
252 target_fd,
253 target_id,
254 slab_size,
255 soft_limit,
256 allocated_count,
257 ))
258 }
259 (_, Some(target_fd)) => {
260 drop(target_fd);
261
262 Err(io::Error::from(io::ErrorKind::InvalidData))
263 }
264 _ => Err(io::Error::from(io::ErrorKind::InvalidData)),
265 }
266 }
267
268 #[inline]
270 pub const fn id(&self) -> ExceptionId {
271 let &Self(_, target_id, ..) = self;
272
273 target_id
274 }
275
276 #[inline]
278 pub const fn slab_size(&self) -> SlabSize {
279 let &Self(_, _, slab_size, ..) = self;
280
281 slab_size
282 }
283
284 #[inline]
286 pub const fn soft_limit(&self) -> SoftSlabLimit {
287 let &Self(_, _, _, soft_limit, ..) = self;
288
289 soft_limit
290 }
291
292 #[inline]
294 pub fn allocated(&self) -> usize {
295 let Self(_, _, _, _, allocated_count) = self;
296
297 allocated_count.load(Ordering::Acquire)
298 }
299
300 #[inline]
307 pub fn map(&self) -> Result<Slab<'_>, SlabAllocationError> {
308 Self::reserve_slab(self)?;
309
310 let Self(target_fd, _, slab_size, ..) = self;
311 let mut record_list = core::ptr::null_mut();
312
313 let target_status = unsafe {
316 binding::catalejo_except_slab_map(
317 target_fd.as_raw_fd(),
318 slab_size.get() as binding::virtual_size_t,
319 &raw mut record_list,
320 )
321 };
322
323 let map_result = status(target_status)
324 .map_err(SlabAllocationError::System)
325 .and_then(|()| {
326 NonNull::new(record_list).ok_or_else(|| {
327 SlabAllocationError::System(io::Error::from(io::ErrorKind::InvalidData))
328 })
329 });
330
331 match map_result {
332 Ok(record_list) => Ok(Slab(self, record_list, SlabState::Editable)),
333 Err(target_error) => {
334 Self::release_slab(self);
335
336 Err(target_error)
337 }
338 }
339 }
340
341 fn reserve_slab(&self) -> Result<(), SlabAllocationError> {
343 let Self(_, _, _, soft_limit, allocated_count) = self;
344 let limit_count = soft_limit.get();
345 let update_result =
346 allocated_count.fetch_update(Ordering::AcqRel, Ordering::Acquire, |allocated_count| {
347 match allocated_count < limit_count {
348 true => Some(allocated_count + 1),
349 false => None,
350 }
351 });
352
353 match update_result {
354 Ok(_) => Ok(()),
355 Err(_) => Err(SlabAllocationError::SoftLimitReached),
356 }
357 }
358
359 fn release_slab(&self) {
361 let Self(_, _, _, _, allocated_count) = self;
362 let previous_count = allocated_count.fetch_sub(1, Ordering::AcqRel);
363
364 debug_assert!(previous_count != 0);
365 }
366}
367
368#[derive(Debug, Clone, Copy, PartialEq, Eq)]
370enum SlabState {
371 Editable,
373 Published,
375}
376
377#[derive(Debug)]
383pub struct Slab<'context>(
384 &'context Context,
386 NonNull<binding::mirilla_except_record>,
388 SlabState,
390);
391
392impl Slab<'_> {
393 #[inline]
395 pub const fn is_published(&self) -> bool {
396 let Self(_, _, slab_state) = self;
397
398 matches!(slab_state, SlabState::Published)
399 }
400
401 #[inline]
403 pub const fn record_list(&self) -> &[binding::mirilla_except_record] {
404 let Self(target_context, record_list, _) = self;
405 let record_count = target_context.slab_size().record_capacity();
406
407 unsafe { slice::from_raw_parts(record_list.as_ptr(), record_count) }
410 }
411
412 #[inline]
414 pub const fn record_list_mut(&mut self) -> Option<&mut [binding::mirilla_except_record]> {
415 let Self(target_context, record_list, slab_state) = self;
416 let record_count = target_context.slab_size().record_capacity();
417
418 match slab_state {
419 SlabState::Editable => {
420 Some(unsafe { slice::from_raw_parts_mut(record_list.as_ptr(), record_count) })
423 }
424 SlabState::Published => None,
425 }
426 }
427
428 #[inline]
437 pub fn publish(&mut self) -> io::Result<()> {
438 let Self(target_context, record_list, slab_state) = self;
439
440 match slab_state {
441 SlabState::Published => Ok(()),
442 SlabState::Editable => {
443 let target_status = unsafe {
446 binding::catalejo_except_slab_publish(
447 record_list.as_ptr(),
448 target_context.slab_size().get() as binding::virtual_size_t,
449 )
450 };
451
452 status(target_status)?;
453 *slab_state = SlabState::Published;
454
455 Ok(())
456 }
457 }
458 }
459
460 #[inline]
468 pub fn edit(&mut self) -> io::Result<()> {
469 let Self(target_context, record_list, slab_state) = self;
470
471 match slab_state {
472 SlabState::Editable => Ok(()),
473 SlabState::Published => {
474 let target_status = unsafe {
477 binding::catalejo_except_slab_edit(
478 record_list.as_ptr(),
479 target_context.slab_size().get() as binding::virtual_size_t,
480 )
481 };
482
483 status(target_status)?;
484 *slab_state = SlabState::Editable;
485
486 Ok(())
487 }
488 }
489 }
490}
491
492impl Drop for Slab<'_> {
493 #[inline]
494 fn drop(&mut self) {
495 let &mut Self(target_context, record_list, _) = self;
496
497 let unmap_status = unsafe {
500 binding::catalejo_except_slab_unmap(
501 record_list.as_ptr(),
502 target_context.slab_size().get() as binding::virtual_size_t,
503 )
504 };
505
506 if unmap_status == 0 {
509 Context::release_slab(target_context);
510 }
511 }
512}
513
514fn status(target_status: core::ffi::c_int) -> io::Result<()> {
516 match target_status {
517 0 => Ok(()),
518 ..=-1 => Err(io::Error::from_raw_os_error(target_status.saturating_abs())),
519 _ => Err(io::Error::from(io::ErrorKind::InvalidData)),
520 }
521}
522
523const _: () = {
524 assert!(core::mem::size_of::<binding::mirilla_except_boundary>() == 16);
525 assert!(core::mem::size_of::<binding::mirilla_except_predicate>() == 16);
526 assert!(core::mem::size_of::<binding::mirilla_except_action>() == 16);
527 assert!(core::mem::size_of::<binding::mirilla_except_record>() == 48);
528 assert!(core::mem::align_of::<binding::mirilla_except_record>() == 16);
529};