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)]
45pub struct SlabSize(
46 NonZero<usize>,
48);
49
50impl SlabSize {
51 pub const DEFAULT: Self = Self(
53 NonZero::new(binding::MIRILLA_EXCEPT_DEFAULT_SLAB_SIZE as usize)
54 .expect("the default slab size is nonzero"),
55 );
56
57 #[inline]
59 pub const fn new(value: usize) -> Result<Self, InvalidSlabSize> {
60 match NonZero::new(value) {
61 None => Err(InvalidSlabSize::Zero),
62 Some(target_size) => {
63 let size_value = target_size.get();
64 let page_aligned = size_value.is_multiple_of(4096);
65 let record_aligned = size_value
66 .is_multiple_of(core::mem::size_of::<binding::mirilla_except_record>());
67 let within_limit = size_value <= binding::MIRILLA_EXCEPT_SLAB_SIZE_LIMIT as usize;
68
69 match (page_aligned, record_aligned, within_limit) {
70 (true, true, true) => Ok(Self(target_size)),
71 (_, _, false) => Err(InvalidSlabSize::TooLarge),
72 _ => Err(InvalidSlabSize::Misaligned),
73 }
74 }
75 }
76 }
77
78 #[inline]
80 pub const fn get(self) -> usize {
81 let Self(value) = self;
82
83 value.get()
84 }
85
86 #[inline]
88 pub const fn record_capacity(self) -> usize {
89 let Self(target_size) = self;
90
91 target_size.get() / core::mem::size_of::<binding::mirilla_except_record>()
92 }
93}
94
95#[derive(Debug, Clone, Copy, Error, PartialEq, Eq)]
97pub enum InvalidSoftSlabLimit {
98 #[error("exception slab limit cannot be zero")]
100 Zero,
101
102 #[error("exception slab limit exceeds the kernel limit")]
104 AboveKernelLimit,
105}
106
107#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
111pub struct SoftSlabLimit(
112 NonZero<usize>,
114);
115
116impl SoftSlabLimit {
117 pub const DEFAULT: Self = Self(NonZero::<usize>::MIN);
119
120 #[inline]
122 pub const fn new(value: usize) -> Result<Self, InvalidSoftSlabLimit> {
123 match NonZero::new(value) {
124 None => Err(InvalidSoftSlabLimit::Zero),
125 Some(value) => match value.get() <= binding::MIRILLA_EXCEPT_SLAB_LIMIT as usize {
126 true => Ok(Self(value)),
127 false => Err(InvalidSoftSlabLimit::AboveKernelLimit),
128 },
129 }
130 }
131
132 #[inline]
134 pub const fn get(self) -> usize {
135 let Self(value) = self;
136
137 value.get()
138 }
139}
140
141#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
145pub struct ExceptionId(
146 NonZero<binding::mirilla_except_id_t>,
148);
149
150impl ExceptionId {
151 #[inline]
153 const fn from_raw(target_id: binding::mirilla_except_id_t) -> Option<Self> {
154 match NonZero::new(target_id) {
155 Some(target_id) => Some(Self(target_id)),
156 None => None,
157 }
158 }
159
160 #[inline]
162 pub const fn get(self) -> binding::mirilla_except_id_t {
163 let Self(value) = self;
164
165 value.get()
166 }
167}
168
169#[derive(Debug, Error)]
171pub enum SlabAllocationError {
172 #[error("exception slab allocation limit reached")]
174 SoftLimitReached,
175
176 #[error("exception slab mapping failed with {0}")]
178 #[error(source(0))]
179 System(
180 io::Error,
182 ),
183}
184
185#[derive(Debug)]
190pub struct Context(
191 OwnedFd,
193 ExceptionId,
195 SlabSize,
197 SoftSlabLimit,
199 AtomicUsize,
201);
202
203impl Context {
204 #[inline]
214 pub unsafe fn create(
215 device: BorrowedFd<'_>,
216 slab_size: SlabSize,
217 soft_limit: SoftSlabLimit,
218 ) -> io::Result<Self> {
219 let mut target_id = 0 as binding::mirilla_except_id_t;
220 let mut target_fd = -1 as RawFd;
221
222 let target_status = unsafe {
225 binding::catalejo_mirilla_except_create(
226 device.as_raw_fd(),
227 slab_size.get() as binding::virtual_size_t,
228 &raw mut target_id,
229 &raw mut target_fd,
230 )
231 };
232
233 status(target_status)?;
234
235 let target_id = ExceptionId::from_raw(target_id);
236 let target_fd = match target_fd {
237 0.. => {
238 Some(unsafe { OwnedFd::from_raw_fd(target_fd) })
240 }
241 _ => None,
242 };
243
244 match (target_id, target_fd) {
245 (Some(target_id), Some(target_fd)) => {
246 let allocated_count = AtomicUsize::new(0);
247
248 Ok(Self(
249 target_fd,
250 target_id,
251 slab_size,
252 soft_limit,
253 allocated_count,
254 ))
255 }
256 (_, Some(target_fd)) => {
257 drop(target_fd);
258
259 Err(io::Error::from(io::ErrorKind::InvalidData))
260 }
261 _ => Err(io::Error::from(io::ErrorKind::InvalidData)),
262 }
263 }
264
265 #[inline]
267 pub const fn id(&self) -> ExceptionId {
268 let &Self(_, target_id, ..) = self;
269
270 target_id
271 }
272
273 #[inline]
275 pub const fn slab_size(&self) -> SlabSize {
276 let &Self(_, _, slab_size, ..) = self;
277
278 slab_size
279 }
280
281 #[inline]
283 pub const fn soft_limit(&self) -> SoftSlabLimit {
284 let &Self(_, _, _, soft_limit, ..) = self;
285
286 soft_limit
287 }
288
289 #[inline]
291 pub fn allocated(&self) -> usize {
292 let Self(_, _, _, _, allocated_count) = self;
293
294 allocated_count.load(Ordering::Acquire)
295 }
296
297 #[inline]
304 pub fn map(&self) -> Result<Slab<'_>, SlabAllocationError> {
305 Self::reserve_slab(self)?;
306
307 let Self(target_fd, _, slab_size, ..) = self;
308 let mut record_list = core::ptr::null_mut();
309
310 let target_status = unsafe {
313 binding::catalejo_except_slab_map(
314 target_fd.as_raw_fd(),
315 slab_size.get() as binding::virtual_size_t,
316 &raw mut record_list,
317 )
318 };
319
320 let map_result = status(target_status)
321 .map_err(SlabAllocationError::System)
322 .and_then(|()| {
323 NonNull::new(record_list).ok_or_else(|| {
324 SlabAllocationError::System(io::Error::from(io::ErrorKind::InvalidData))
325 })
326 });
327
328 match map_result {
329 Ok(record_list) => Ok(Slab(self, record_list, SlabState::Editable)),
330 Err(target_error) => {
331 Self::release_slab(self);
332
333 Err(target_error)
334 }
335 }
336 }
337
338 fn reserve_slab(&self) -> Result<(), SlabAllocationError> {
340 let Self(_, _, _, soft_limit, allocated_count) = self;
341 let limit_count = soft_limit.get();
342 let update_result =
343 allocated_count.fetch_update(Ordering::AcqRel, Ordering::Acquire, |allocated_count| {
344 match allocated_count < limit_count {
345 true => Some(allocated_count + 1),
346 false => None,
347 }
348 });
349
350 match update_result {
351 Ok(_) => Ok(()),
352 Err(_) => Err(SlabAllocationError::SoftLimitReached),
353 }
354 }
355
356 fn release_slab(&self) {
358 let Self(_, _, _, _, allocated_count) = self;
359 let previous_count = allocated_count.fetch_sub(1, Ordering::AcqRel);
360
361 debug_assert!(previous_count != 0);
362 }
363}
364
365#[derive(Debug, Clone, Copy, PartialEq, Eq)]
367enum SlabState {
368 Editable,
370 Published,
372}
373
374#[derive(Debug)]
378pub struct Slab<'context>(
379 &'context Context,
381 NonNull<binding::mirilla_except_record>,
383 SlabState,
385);
386
387impl Slab<'_> {
388 #[inline]
390 pub const fn is_published(&self) -> bool {
391 let Self(_, _, slab_state) = self;
392
393 matches!(slab_state, SlabState::Published)
394 }
395
396 #[inline]
398 pub const fn record_list(&self) -> &[binding::mirilla_except_record] {
399 let Self(target_context, record_list, _) = self;
400 let record_count = target_context.slab_size().record_capacity();
401
402 unsafe { slice::from_raw_parts(record_list.as_ptr(), record_count) }
405 }
406
407 #[inline]
409 pub const fn record_list_mut(&mut self) -> Option<&mut [binding::mirilla_except_record]> {
410 let Self(target_context, record_list, slab_state) = self;
411 let record_count = target_context.slab_size().record_capacity();
412
413 match slab_state {
414 SlabState::Editable => {
415 Some(unsafe { slice::from_raw_parts_mut(record_list.as_ptr(), record_count) })
418 }
419 SlabState::Published => None,
420 }
421 }
422
423 #[inline]
432 pub fn publish(&mut self) -> io::Result<()> {
433 let Self(target_context, record_list, slab_state) = self;
434
435 match slab_state {
436 SlabState::Published => Ok(()),
437 SlabState::Editable => {
438 let target_status = unsafe {
441 binding::catalejo_except_slab_publish(
442 record_list.as_ptr(),
443 target_context.slab_size().get() as binding::virtual_size_t,
444 )
445 };
446
447 status(target_status)?;
448 *slab_state = SlabState::Published;
449
450 Ok(())
451 }
452 }
453 }
454
455 #[inline]
463 pub fn edit(&mut self) -> io::Result<()> {
464 let Self(target_context, record_list, slab_state) = self;
465
466 match slab_state {
467 SlabState::Editable => Ok(()),
468 SlabState::Published => {
469 let target_status = unsafe {
472 binding::catalejo_except_slab_edit(
473 record_list.as_ptr(),
474 target_context.slab_size().get() as binding::virtual_size_t,
475 )
476 };
477
478 status(target_status)?;
479 *slab_state = SlabState::Editable;
480
481 Ok(())
482 }
483 }
484 }
485}
486
487impl Drop for Slab<'_> {
488 #[inline]
489 fn drop(&mut self) {
490 let &mut Self(target_context, record_list, _) = self;
491
492 let unmap_status = unsafe {
495 binding::catalejo_except_slab_unmap(
496 record_list.as_ptr(),
497 target_context.slab_size().get() as binding::virtual_size_t,
498 )
499 };
500
501 if unmap_status == 0 {
504 Context::release_slab(target_context);
505 }
506 }
507}
508
509fn status(target_status: core::ffi::c_int) -> io::Result<()> {
511 match target_status {
512 0 => Ok(()),
513 ..=-1 => Err(io::Error::from_raw_os_error(target_status.saturating_abs())),
514 _ => Err(io::Error::from(io::ErrorKind::InvalidData)),
515 }
516}
517
518const _: () = {
519 assert!(core::mem::size_of::<binding::mirilla_except_boundary>() == 16);
520 assert!(core::mem::size_of::<binding::mirilla_except_predicate>() == 16);
521 assert!(core::mem::size_of::<binding::mirilla_except_action>() == 16);
522 assert!(core::mem::size_of::<binding::mirilla_except_record>() == 48);
523 assert!(core::mem::align_of::<binding::mirilla_except_record>() == 16);
524};