Skip to main content

strat9_abi/
data.rs

1//! ABI data structures shared between kernel and userspace.
2//!
3//! These types define the wire format for syscalls, IPC messages, and
4//! file system operations. Both kernel and userspace must agree on the
5//! exact memory layout (size, alignment, field ordering).
6
7use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout};
8
9// ── IPC Message Constants ───────────────────────────────────────────────────
10
11/// Total size of an IPC message in bytes (including header and payload).
12pub const IPC_MESSAGE_SIZE: usize = 256;
13
14/// Alignment requirement for IPC messages (64-byte aligned for cache line).
15pub const IPC_MESSAGE_ALIGN: usize = 64;
16
17/// Size of the IPC message header (sender + msg_type + flags).
18pub const IPC_MESSAGE_HEADER_SIZE: usize = 16;
19
20/// Maximum payload size in an IPC message (256 - 16 = 240 bytes).
21pub const IPC_PAYLOAD_CAPACITY: usize = IPC_MESSAGE_SIZE - IPC_MESSAGE_HEADER_SIZE;
22
23// ── IPC File Flags ──────────────────────────────────────────────────────────
24
25/// Directory entry flag: this entry is a directory.
26pub const IPC_FILE_FLAG_DIRECTORY: u32 = 1 << 0;
27
28/// Directory entry flag: this entry is a device file.
29pub const IPC_FILE_FLAG_DEVICE: u32 = 1 << 1;
30
31/// Directory entry flag: this entry is a pipe.
32pub const IPC_FILE_FLAG_PIPE: u32 = 1 << 2;
33
34/// Directory entry flag: file is opened in append mode.
35pub const IPC_FILE_FLAG_APPEND: u32 = 1 << 3;
36
37/// Directory entry flag: supports chunked reads.
38pub const IPC_FILE_FLAG_CHUNK_READ: u32 = 1 << 4;
39
40/// Directory entry flag: supports chunked writes.
41pub const IPC_FILE_FLAG_CHUNK_WRITE: u32 = 1 << 5;
42
43// ── SiloMode ────────────────────────────────────────────────────────────────
44
45/// 9-bit octal silo permission mode (3 control + 3 hardware + 3 registry).
46///
47/// Shared ABI type used by both kernel and userspace init.
48/// The kernel's richer `OctalMode` (with typed bitflag fields)
49/// is built from this via `OctalMode::from_octal(val.0)`.
50///
51/// Bit layout: `[control:3][hardware:3][registry:3]` (LSB = registry).
52///
53/// Example: `0o777` = full permissions in all groups.
54#[derive(Debug, Clone, Copy, PartialEq, Eq, FromBytes, IntoBytes, Immutable)]
55#[repr(transparent)]
56pub struct SiloMode(pub u16);
57
58impl SiloMode {
59    /// Return true if `self` bits are a subset of `other`'s bits
60    /// (i.e. `self` does not request any permission `other` lacks).
61    pub const fn is_subset_of(&self, other: &SiloMode) -> bool {
62        let (s_c, s_h, s_r) = ((self.0 >> 6) & 0o7, (self.0 >> 3) & 0o7, self.0 & 0o7);
63        let (o_c, o_h, o_r) = ((other.0 >> 6) & 0o7, (other.0 >> 3) & 0o7, other.0 & 0o7);
64        (s_c & !o_c) == 0 && (s_h & !o_h) == 0 && (s_r & !o_r) == 0
65    }
66}
67
68// ── TimeSpec ───────────────── saturating multiplication─────────────────────
69
70/// POSIX-style timestamp: seconds + nanoseconds.
71///
72/// Used for `SYS_CLOCK_GETTIME`, `SYS_NANOSLEEP`, and file timestamps.
73#[derive(Debug, Clone, Copy, FromBytes, IntoBytes, Immutable)]
74#[repr(C)]
75pub struct TimeSpec {
76    /// Seconds since Unix epoch (or relative time for sleep).
77    pub tv_sec: i64,
78    /// Nanoseconds (0..999_999_999).
79    pub tv_nsec: i64,
80}
81
82impl TimeSpec {
83    /// Return a zero-initialized timestamp.
84    pub const fn zero() -> Self {
85        Self {
86            tv_sec: 0,
87            tv_nsec: 0,
88        }
89    }
90
91    /// Convert the timestamp to nanoseconds with saturating arithmetic.
92    ///
93    /// A timestamp with any negative component is invalid as a duration
94    /// and yields `0`: components previously cast individually into `u64`,
95    /// so a userspace-supplied negative `nanosleep` wrapped into a
96    /// near-infinite sleep. Callers that must reject invalid durations
97    /// should use [`TimeSpec::checked_to_nanos`] instead.
98    pub fn to_nanos(&self) -> u64 {
99        if self.tv_sec < 0 || self.tv_nsec < 0 {
100            return 0;
101        }
102        let secs = self.tv_sec as u64;
103        let nsecs = self.tv_nsec as u64;
104        secs.saturating_mul(1_000_000_000).saturating_add(nsecs)
105    }
106
107    /// Convert to nanoseconds, returning `None` when any component is
108    /// negative (instead of silently clamping like [`TimeSpec::to_nanos`]).
109    /// Use this in paths that must surface `EINVAL` on invalid input.
110    pub fn checked_to_nanos(&self) -> Option<u64> {
111        if self.tv_sec < 0 || self.tv_nsec < 0 {
112            return None;
113        }
114        Some(self.to_nanos())
115    }
116
117    /// Build a timestamp from a nanoseconds value.
118    pub fn from_nanos(nanos: u64) -> Self {
119        Self {
120            tv_sec: (nanos / 1_000_000_000) as i64,
121            tv_nsec: (nanos % 1_000_000_000) as i64,
122        }
123    }
124}
125
126// ── Stat (legacy, kept for compat) ─────────────────────────────────────────
127
128/// Legacy stat structure (120 bytes). Prefer [`FileStat`] for new code.
129#[derive(Debug, Clone, Copy, FromBytes, IntoBytes, Immutable)]
130#[repr(C)]
131pub struct Stat {
132    pub st_dev: u64,
133    pub st_ino: u64,
134    pub st_nlink: u64,
135    pub st_mode: u32,
136    pub st_uid: u32,
137    pub st_gid: u32,
138    pub _padding0: u32,
139    pub st_rdev: u64,
140    pub st_size: u64,
141    pub st_blksize: u64,
142    pub st_blocks: u64,
143    pub st_atime: TimeSpec,
144    pub st_mtime: TimeSpec,
145    pub st_ctime: TimeSpec,
146}
147
148// ── StatVfs ────────────────────────────────────────────────────────────────
149
150/// Filesystem statistics (for `SYS_STAT` on directories).
151///
152/// Similar to Linux `struct statfs`.
153#[derive(Debug, Clone, Copy, FromBytes, IntoBytes, Immutable)]
154#[repr(C)]
155pub struct StatVfs {
156    /// Preferred file system block size.
157    pub f_bsize: u64,
158    /// File system fragment size.
159    pub f_frsize: u64,
160    /// Total data blocks in the file system.
161    pub f_blocks: u64,
162    /// Free blocks available to unprivileged users.
163    pub f_bfree: u64,
164    /// Free blocks available to unprivileged users.
165    pub f_bavail: u64,
166    /// Total file nodes (inodes).
167    pub f_files: u64,
168    /// Free file nodes.
169    pub f_ffree: u64,
170    /// Free file nodes available to unprivileged users.
171    pub f_favail: u64,
172    /// File system ID.
173    pub f_fsid: u64,
174    /// File system flags (read-only, etc.).
175    pub f_flag: u64,
176    /// Maximum filename length.
177    pub f_namemax: u64,
178}
179
180// ── Map (mmap) ─────────────────────────────────────────────────────────────
181
182/// Memory mapping descriptor for `SYS_MMAP`.
183///
184/// Describes a region of virtual memory mapped into a process.
185#[derive(Debug, Clone, Copy, FromBytes, IntoBytes, Immutable)]
186#[repr(C)]
187pub struct Map {
188    /// Offset into the backing file (must be page-aligned).
189    pub offset: usize,
190    /// Size of the mapping in bytes.
191    pub size: usize,
192    /// Protection flags (`PROT_READ`, `PROT_WRITE`, `PROT_EXEC`).
193    pub flags: u32,
194    pub _reserved: u32,
195    /// Virtual address of the mapping.
196    pub addr: usize,
197}
198
199// ── HandleInfo ──────────────────────────────────────────────────────────────
200
201/// Information about a capability handle (returned by `SYS_HANDLE_INFO`).
202#[derive(Debug, Clone, Copy, FromBytes, IntoBytes, Immutable)]
203#[repr(C)]
204pub struct HandleInfo {
205    /// Resource type (file, memory, IPC, etc.).
206    pub resource_type: u32,
207    /// Permission bits (read, write, execute, grant, revoke).
208    pub permissions: u32,
209    /// Underlying resource identifier (fd, memory region ID, etc.).
210    pub resource: u64,
211}
212
213// ── MemoryRegionInfo ────────────────────────────────────────────────────────
214
215/// Information about an exported memory region (returned by `SYS_MEM_REGION_INFO`).
216#[derive(Debug, Clone, Copy, FromBytes, IntoBytes, Immutable)]
217#[repr(C)]
218pub struct MemoryRegionInfo {
219    /// Total size of the region in bytes.
220    pub size: u64,
221    /// Page size used for mapping.
222    pub page_size: u64,
223    /// Region flags (read, write, execute).
224    pub flags: u32,
225    pub _reserved: u32,
226}
227
228// ── AsyncRingLayout ─────────────────────────────────────────────────────────
229
230/// Layout descriptor for async I/O ring buffers.
231///
232/// Describes the submission and completion queue regions within
233/// a shared memory page used by the async I/O subsystem.
234#[derive(Debug, Clone, Copy, FromBytes, IntoBytes, Immutable, KnownLayout)]
235#[repr(C)]
236pub struct AsyncRingLayout {
237    /// Physical address of the submission queue base.
238    pub sq_base: u64,
239    /// Physical address of the completion queue base.
240    pub cq_base: u64,
241    /// Size of the submission queue in bytes.
242    pub sq_size: u64,
243    /// Size of the completion queue in bytes.
244    pub cq_size: u64,
245    /// Number of entries in each queue.
246    pub entries: u32,
247    pub _reserved: u32,
248}
249
250// ── PCI ─────────────────────────────────────────────────────────────────────
251
252/// PCI match flag: match by vendor ID.
253pub const PCI_MATCH_VENDOR_ID: u32 = 1 << 0;
254
255/// PCI match flag: match by device ID.
256pub const PCI_MATCH_DEVICE_ID: u32 = 1 << 1;
257
258/// PCI match flag: match by class code.
259pub const PCI_MATCH_CLASS_CODE: u32 = 1 << 2;
260
261/// PCI match flag: match by subclass.
262pub const PCI_MATCH_SUBCLASS: u32 = 1 << 3;
263
264/// PCI match flag: match by programming interface.
265pub const PCI_MATCH_PROG_IF: u32 = 1 << 4;
266
267/// PCI device address (bus/device/function).
268#[derive(Debug, Clone, Copy, FromBytes, IntoBytes, Immutable)]
269#[repr(C, align(4))]
270pub struct PciAddress {
271    /// PCI bus number (0-255).
272    pub bus: u8,
273    /// PCI device number (0-31).
274    pub device: u8,
275    /// PCI function number (0-7).
276    pub function: u8,
277    pub _reserved: u8,
278}
279
280/// PCI device search criteria for `SYS_PCI_ENUM`.
281///
282/// Set `match_flags` to indicate which fields to match. Fields not
283/// flagged are ignored.
284#[derive(Debug, Clone, Copy, FromBytes, IntoBytes, Immutable)]
285#[repr(C)]
286pub struct PciProbeCriteria {
287    /// Bitmask of fields to match (see `PCI_MATCH_*` constants).
288    pub match_flags: u32,
289    /// Vendor ID to match (if `PCI_MATCH_VENDOR_ID` is set).
290    pub vendor_id: u16,
291    /// Device ID to match (if `PCI_MATCH_DEVICE_ID` is set).
292    pub device_id: u16,
293    /// Class code to match (if `PCI_MATCH_CLASS_CODE` is set).
294    pub class_code: u8,
295    /// Subclass to match (if `PCI_MATCH_SUBCLASS` is set).
296    pub subclass: u8,
297    /// Programming interface to match (if `PCI_MATCH_PROG_IF` is set).
298    pub prog_if: u8,
299    pub _reserved: u8,
300}
301
302/// PCI device information returned by `SYS_PCI_ENUM`.
303#[derive(Debug, Clone, Copy, FromBytes, IntoBytes, Immutable)]
304#[repr(C)]
305pub struct PciDeviceInfo {
306    /// PCI bus/device/function address.
307    pub address: PciAddress,
308    /// Vendor ID (e.g., 0x8086 for Intel).
309    pub vendor_id: u16,
310    /// Device ID (e.g., 0x100E for Intel E1000).
311    pub device_id: u16,
312    /// PCI class code (e.g., 0x02 for network controller).
313    pub class_code: u8,
314    /// PCI subclass (e.g., 0x00 for Ethernet controller).
315    pub subclass: u8,
316    /// Programming interface (e.g., 0x00 for E1000).
317    pub prog_if: u8,
318    /// PCI revision ID.
319    pub revision: u8,
320    /// Header type (0 = standard, 1 = PCI-to-PCI bridge).
321    pub header_type: u8,
322    /// Interrupt line (IRQ number, 0 = none).
323    pub interrupt_line: u8,
324    /// Interrupt pin (A=1, B=2, C=3, D=4, 0 = none).
325    pub interrupt_pin: u8,
326    pub _reserved: u8,
327}
328
329// ── FileStat ────────────────────────────────────────────────────────────────
330
331/// File status information (returned by `SYS_FSTAT`, `SYS_STAT`, `SYS_FSTATAT`).
332///
333/// Equivalent to POSIX `struct stat` with 64-bit fields.
334#[derive(Debug, Clone, Copy, FromBytes, IntoBytes, Immutable)]
335#[repr(C)]
336pub struct FileStat {
337    /// Device ID containing this file.
338    pub st_dev: u64,
339    /// Inode number.
340    pub st_ino: u64,
341    /// File type and permissions (see `DT_*` and `0o7777` masks).
342    pub st_mode: u32,
343    /// Number of hard links.
344    pub st_nlink: u32,
345    /// Owner user ID.
346    pub st_uid: u32,
347    /// Owner group ID.
348    pub st_gid: u32,
349    /// Device ID (for special files).
350    pub st_rdev: u64,
351    /// Total size in bytes.
352    pub st_size: u64,
353    /// Preferred block size for I/O.
354    pub st_blksize: u64,
355    /// Number of 512-byte blocks allocated.
356    pub st_blocks: u64,
357    /// Last access time.
358    pub st_atime: TimeSpec,
359    /// Last modification time.
360    pub st_mtime: TimeSpec,
361    /// Last status change time.
362    pub st_ctime: TimeSpec,
363}
364
365impl FileStat {
366    /// Return a fully zeroed `FileStat`.
367    pub const fn zeroed() -> Self {
368        FileStat {
369            st_dev: 0,
370            st_ino: 0,
371            st_mode: 0,
372            st_nlink: 0,
373            st_uid: 0,
374            st_gid: 0,
375            st_rdev: 0,
376            st_size: 0,
377            st_blksize: 0,
378            st_blocks: 0,
379            st_atime: TimeSpec::zero(),
380            st_mtime: TimeSpec::zero(),
381            st_ctime: TimeSpec::zero(),
382        }
383    }
384
385    /// Return true when the mode encodes a directory.
386    pub fn is_dir(&self) -> bool {
387        (self.st_mode & 0o170000) == 0o040000
388    }
389
390    /// Return true when the mode encodes a regular file.
391    pub fn is_file(&self) -> bool {
392        (self.st_mode & 0o170000) == 0o100000
393    }
394}
395
396// ── IpcMessage ─────────────────────────────────────────────────────────────
397
398/// Fixed-size IPC message (256 bytes, 64-byte aligned).
399///
400/// Used by IPC ports, channels, and the transport layer.
401/// The header occupies 16 bytes; the payload is 240 bytes.
402#[derive(Clone, Copy, FromBytes, IntoBytes, Immutable)]
403#[repr(C, align(64))]
404pub struct IpcMessage {
405    /// PID/TID of the sending process.
406    pub sender: u64,
407    /// Message type identifier (protocol-specific).
408    pub msg_type: u32,
409    /// Message flags (protocol-specific).
410    pub flags: u32,
411    /// Payload data (up to 240 bytes).
412    pub payload: [u8; IPC_PAYLOAD_CAPACITY],
413}
414
415impl IpcMessage {
416    /// Total wire size of the message (including header).
417    pub const WIRE_SIZE: usize = IPC_MESSAGE_SIZE;
418    /// Alignment requirement.
419    pub const ALIGN: usize = IPC_MESSAGE_ALIGN;
420    /// Maximum payload bytes.
421    pub const PAYLOAD_CAPACITY: usize = IPC_PAYLOAD_CAPACITY;
422
423    /// Message type used by scheme servers for every reply
424    /// (success or errno-style status).
425    pub const REPLY_MSG_TYPE: u32 = 0x80;
426
427    /// Usable payload capacity for `OPEN` inline path (240 - 6 bytes overhead).
428    pub const OPEN_INLINE_CAPACITY: usize = IPC_PAYLOAD_CAPACITY - 6;
429    /// Usable payload capacity for `UNLINK` inline path (240 - 2 bytes overhead).
430    pub const UNLINK_INLINE_CAPACITY: usize = IPC_PAYLOAD_CAPACITY - 2;
431    /// Usable payload capacity for `READ` inline path (240 - 8 bytes overhead).
432    pub const READ_INLINE_CAPACITY: usize = IPC_PAYLOAD_CAPACITY - 8;
433    /// Usable payload capacity for `WRITE` inline path (240 - 18 bytes overhead).
434    pub const WRITE_INLINE_CAPACITY: usize = IPC_PAYLOAD_CAPACITY - 18;
435
436    /// Create an empty IPC message for `msg_type`.
437    pub const fn new(msg_type: u32) -> Self {
438        IpcMessage {
439            sender: 0,
440            msg_type,
441            flags: 0,
442            payload: [0u8; IPC_PAYLOAD_CAPACITY],
443        }
444    }
445
446    /// Build a standard error reply carrying a status code in payload.
447    ///
448    /// The `msg_type` is set to `0x80` (error reply marker).
449    /// The payload contains the error code as a little-endian `i32`.
450    pub fn error_reply(sender: u64, status: i32) -> Self {
451        let mut msg = IpcMessage::new(0x80);
452        msg.sender = sender;
453        msg.payload[0..4].copy_from_slice(&(status as u32).to_le_bytes());
454        msg
455    }
456
457    /// Build a reply with `msg_type = 0x80` and a `u32` errno-style status
458    /// (`0` = success) in `payload[0..4]`.
459    ///
460    /// Single source of truth for scheme servers, replacing per-server
461    /// `ok_reply`/`err_reply` duplicates.
462    pub fn status_reply(sender: u64, status: u32) -> Self {
463        let mut msg = IpcMessage::new(Self::REPLY_MSG_TYPE);
464        msg.sender = sender;
465        msg.payload[0..4].copy_from_slice(&status.to_le_bytes());
466        msg
467    }
468}
469
470impl core::fmt::Debug for IpcMessage {
471    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
472        f.debug_struct("IpcMessage")
473            .field("sender", &self.sender)
474            .field("msg_type", &format_args!("0x{:02x}", self.msg_type))
475            .field("flags", &self.flags)
476            .finish()
477    }
478}
479
480// ── Seek constants ──────────────────────────────────────────────────────────
481
482/// Seek relative to the beginning of the file.
483pub const SEEK_SET: usize = 0;
484
485/// Seek relative to the current file position.
486pub const SEEK_CUR: usize = 1;
487
488/// Seek relative to the end of the file.
489pub const SEEK_END: usize = 2;
490
491// ── File type constants ─────────────────────────────────────────────────────
492
493/// Unknown file type.
494pub const DT_UNKNOWN: u8 = 0;
495
496/// FIFO (named pipe).
497pub const DT_FIFO: u8 = 1;
498
499/// Character device.
500pub const DT_CHR: u8 = 2;
501
502/// Directory.
503pub const DT_DIR: u8 = 4;
504
505/// Block device.
506pub const DT_BLK: u8 = 6;
507
508/// Regular file.
509pub const DT_REG: u8 = 8;
510
511/// Symbolic link.
512pub const DT_LNK: u8 = 10;
513
514/// Unix domain socket.
515pub const DT_SOCK: u8 = 12;
516
517// ── DirentHeader ────────────────────────────────────────────────────────────
518
519/// Fixed-size header for each directory entry in the `SYS_GETDENTS` wire format.
520///
521/// Wire layout per entry: `DirentHeader` (12 bytes) followed by `name_len`
522/// bytes of filename data and a trailing NUL byte.
523///
524/// Total entry size = 12 + name_len + 1 bytes.
525#[derive(Debug, Clone, Copy, FromBytes, IntoBytes, Immutable)]
526#[repr(C, packed)]
527pub struct DirentHeader {
528    /// Inode number of the entry.
529    pub ino: u64,
530    /// File type (see `DT_*` constants).
531    pub file_type: u8,
532    /// Length of the filename in bytes (excluding NUL).
533    pub name_len: u16,
534    pub _padding: u8,
535}
536
537impl DirentHeader {
538    /// Size of the fixed header portion (12 bytes).
539    pub const SIZE: usize = 12; // 8 + 1 + 2 + 1
540
541    /// Return the total packed entry size (header + name + trailing NUL).
542    pub const fn entry_size(&self) -> usize {
543        Self::SIZE + self.name_len as usize + 1
544    }
545}
546
547// ── SiloConfig ──────────────────────────────────────────────────────────────
548
549/// Silo configuration block passed from init to `SYS_SILO_CREATE`.
550///
551/// Defines resource limits, capabilities, and scheduling parameters
552/// for a new silo (process isolation container).
553///
554/// Layout must match the kernel's definition (ABI contract).
555#[derive(Debug, Clone, Copy)]
556#[repr(C)]
557pub struct SiloConfig {
558    /// Minimum memory reservation in bytes.
559    pub mem_min: u64,
560    /// Maximum memory limit in bytes (0 = unlimited).
561    pub mem_max: u64,
562    /// CPU scheduling weight (relative priority).
563    pub cpu_shares: u32,
564    /// CPU time quota per period in microseconds (0 = unlimited).
565    pub cpu_quota_us: u64,
566    /// CPU scheduling period in microseconds.
567    pub cpu_period_us: u64,
568    /// CPU affinity bitmask (0 = any CPU).
569    pub cpu_affinity_mask: u64,
570    /// Maximum number of threads/tasks in the silo.
571    pub max_tasks: u32,
572    /// Read bandwidth limit in bytes/sec (0 = unlimited).
573    pub io_bw_read: u64,
574    /// Write bandwidth limit in bytes/sec (0 = unlimited).
575    pub io_bw_write: u64,
576    /// Pointer to the initial capability list.
577    pub caps_ptr: u64,
578    /// Length of the capability list in bytes.
579    pub caps_len: u64,
580    /// Silo behavior flags.
581    pub flags: u64,
582    /// Silo ID (assigned by kernel, 0 = auto-assign).
583    pub sid: u32,
584    /// Octal permission mode (see `SiloMode`).
585    pub mode: u16,
586    /// Silo family/type identifier.
587    pub family: u8,
588    /// Required CPU features (bitmask from CPUID).
589    pub cpu_features_required: u64,
590    /// Allowed CPU features (bitmask, 0 = all allowed).
591    pub cpu_features_allowed: u64,
592    /// XCR0 register mask for FPU/SSE/AVX state.
593    pub xcr0_mask: u64,
594    /// Maximum concurrent graphics sessions (0 = no graphics).
595    pub graphics_max_sessions: u16,
596    /// Graphics session time-to-live in seconds.
597    pub graphics_session_ttl_sec: u32,
598    pub graphics_reserved: u16,
599}
600
601impl SiloConfig {
602    /// Return a zero-initialized silo configuration.
603    pub const fn zero() -> Self {
604        Self {
605            mem_min: 0,
606            mem_max: 0,
607            cpu_shares: 0,
608            cpu_quota_us: 0,
609            cpu_period_us: 0,
610            cpu_affinity_mask: 0,
611            max_tasks: 0,
612            io_bw_read: 0,
613            io_bw_write: 0,
614            caps_ptr: 0,
615            caps_len: 0,
616            flags: 0,
617            sid: 0,
618            mode: 0,
619            family: 0,
620            cpu_features_required: 0,
621            cpu_features_allowed: u64::MAX,
622            xcr0_mask: 0,
623            graphics_max_sessions: 0,
624            graphics_session_ttl_sec: 0,
625            graphics_reserved: 0,
626        }
627    }
628}
629
630// ── Static assertions ──────────────────────────────────────────────────────
631
632macro_rules! assert_abi_struct {
633    ($t:ty, $size:expr, $align:expr) => {
634        static_assertions::assert_eq_size!($t, [u8; $size]);
635        static_assertions::const_assert_eq!(core::mem::align_of::<$t>(), $align);
636    };
637}
638
639assert_abi_struct!(DirentHeader, 12, 1);
640assert_abi_struct!(Stat, 120, 8);
641assert_abi_struct!(StatVfs, 88, 8);
642assert_abi_struct!(Map, 32, 8);
643assert_abi_struct!(FileStat, 112, 8);
644assert_abi_struct!(IpcMessage, IPC_MESSAGE_SIZE, IPC_MESSAGE_ALIGN);
645assert_abi_struct!(TimeSpec, 16, 8);
646assert_abi_struct!(HandleInfo, 16, 8);
647assert_abi_struct!(MemoryRegionInfo, 24, 8);
648assert_abi_struct!(PciAddress, 4, 4);
649assert_abi_struct!(PciProbeCriteria, 12, 4);
650assert_abi_struct!(PciDeviceInfo, 16, 4);