Skip to main content

air_sys_syscall/io_uring/
raw.rs

1// This Source Code Form is subject to the terms of the Mozilla Public
2// License, v. 2.0. If a copy of the MPL was not distributed with this
3// file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
5//! Niveau brut du module io_uring : numéros de syscalls, wrappers `asm!` des
6//! trois appels (`io_uring_setup`/`enter`/`register`) et miroirs `#[repr(C)]`
7//! des structures kernel du **protocole d'anneau** (cible 6.12).
8//!
9//! Les champs des structures conservent leurs **noms kernel** (ADR-029, zone
10//! d'interface) pour le cross-référencement avec `io_uring.h`. Ce module est
11//! **interne au crate** (`pub(crate)`) au Temps 1 ; l'exposition publique du
12//! protocole brut relève du Temps 4.
13//!
14//! > Périmètre Temps 1 : seules les structures nécessaires au cœur sont posées
15//! > (`io_uring_params` + offsets, `io_uring_sqe` 64 o, `io_uring_cqe` 16 o,
16//! > `io_uring_getevents_arg`, `io_uring_sync_cancel_reg`, `io_uring_probe`).
17//! > Les structures `register` avancées (buffers/files/napi…) arrivent au
18//! > Temps 3a.
19
20use air_sys_types::Errno;
21
22// ─────────────────────────────────────────────────────────────────────────
23// Numéros de syscalls (identiques x86_64 et aarch64 — ABI asm-generic)
24// ─────────────────────────────────────────────────────────────────────────
25
26/// `io_uring_setup(2)` — construction du ring (§5).
27pub(crate) const NR_IO_URING_SETUP: i64 = 425;
28/// `io_uring_enter(2)` — soumission + attente (§6, §7).
29pub(crate) const NR_IO_URING_ENTER: i64 = 426;
30/// `io_uring_register(2)` — probe, sync_cancel, ressources (§8, §9, Temps 3a).
31pub(crate) const NR_IO_URING_REGISTER: i64 = 427;
32
33// ─────────────────────────────────────────────────────────────────────────
34// Offsets de mmap (§3.1)
35// ─────────────────────────────────────────────────────────────────────────
36
37/// `IORING_OFF_SQ_RING` : offset de mmap de l'anneau SQ.
38pub(crate) const IORING_OFF_SQ_RING: u64 = 0;
39/// `IORING_OFF_CQ_RING` : offset de mmap de l'anneau CQ.
40pub(crate) const IORING_OFF_CQ_RING: u64 = 0x0800_0000;
41/// `IORING_OFF_SQES` : offset de mmap du tableau de SQE.
42pub(crate) const IORING_OFF_SQES: u64 = 0x1000_0000;
43/// `IORING_OFF_PBUF_RING` : base d'offset de mmap d'un anneau de buffers fournis
44/// (Temps 3b, mode `kernel_mmap`) ; combiné à `(bgid << IORING_OFF_PBUF_SHIFT)`.
45pub(crate) const IORING_OFF_PBUF_RING: u64 = 0x8000_0000;
46/// `IORING_OFF_PBUF_SHIFT` : décalage du `group_id` dans l'offset de mmap PBUF.
47pub(crate) const IORING_OFF_PBUF_SHIFT: u32 = 16;
48
49// ─────────────────────────────────────────────────────────────────────────
50// Features `IORING_FEAT_*` (axe G, bits 0..=15 en 6.12)
51// ─────────────────────────────────────────────────────────────────────────
52
53/// `IORING_FEAT_SINGLE_MMAP`.
54pub(crate) const IORING_FEAT_SINGLE_MMAP: u32 = 1 << 0;
55/// `IORING_FEAT_NODROP`.
56pub(crate) const IORING_FEAT_NODROP: u32 = 1 << 1;
57/// `IORING_FEAT_SUBMIT_STABLE`.
58pub(crate) const IORING_FEAT_SUBMIT_STABLE: u32 = 1 << 2;
59/// `IORING_FEAT_RW_CUR_POS`.
60pub(crate) const IORING_FEAT_RW_CUR_POS: u32 = 1 << 3;
61/// `IORING_FEAT_CUR_PERSONALITY`.
62pub(crate) const IORING_FEAT_CUR_PERSONALITY: u32 = 1 << 4;
63/// `IORING_FEAT_FAST_POLL`.
64pub(crate) const IORING_FEAT_FAST_POLL: u32 = 1 << 5;
65/// `IORING_FEAT_POLL_32BITS`.
66pub(crate) const IORING_FEAT_POLL_32BITS: u32 = 1 << 6;
67/// `IORING_FEAT_SQPOLL_NONFIXED`.
68pub(crate) const IORING_FEAT_SQPOLL_NONFIXED: u32 = 1 << 7;
69/// `IORING_FEAT_EXT_ARG`.
70pub(crate) const IORING_FEAT_EXT_ARG: u32 = 1 << 8;
71/// `IORING_FEAT_NATIVE_WORKERS`.
72pub(crate) const IORING_FEAT_NATIVE_WORKERS: u32 = 1 << 9;
73/// `IORING_FEAT_RSRC_TAGS`.
74pub(crate) const IORING_FEAT_RSRC_TAGS: u32 = 1 << 10;
75/// `IORING_FEAT_CQE_SKIP`.
76pub(crate) const IORING_FEAT_CQE_SKIP: u32 = 1 << 11;
77/// `IORING_FEAT_LINKED_FILE`.
78pub(crate) const IORING_FEAT_LINKED_FILE: u32 = 1 << 12;
79/// `IORING_FEAT_REG_REG_RING`.
80pub(crate) const IORING_FEAT_REG_REG_RING: u32 = 1 << 13;
81/// `IORING_FEAT_RECVSEND_BUNDLE`.
82pub(crate) const IORING_FEAT_RECVSEND_BUNDLE: u32 = 1 << 14;
83/// `IORING_FEAT_MIN_TIMEOUT`.
84pub(crate) const IORING_FEAT_MIN_TIMEOUT: u32 = 1 << 15;
85
86// ─────────────────────────────────────────────────────────────────────────
87// Flags d'anneau SQ (`IORING_SQ_*`) et d'`io_uring_enter` (axe F)
88// ─────────────────────────────────────────────────────────────────────────
89
90/// `IORING_SQ_CQ_OVERFLOW` : la CQ a débordé.
91pub(crate) const IORING_SQ_CQ_OVERFLOW: u32 = 1 << 1;
92/// `IORING_SQ_NEED_WAKEUP` (bit 0 des flags SQ, Temps 3e) : le thread kernel
93/// `SQPOLL` s'est endormi (après `idle`) et doit être réveillé par un
94/// `io_uring_enter(.., SQ_WAKEUP)` à la prochaine soumission.
95pub(crate) const IORING_SQ_NEED_WAKEUP: u32 = 1 << 0;
96
97/// `IORING_ENTER_GETEVENTS` : attendre des complétions.
98pub(crate) const IORING_ENTER_GETEVENTS: u32 = 1 << 0;
99/// `IORING_ENTER_SQ_WAKEUP` (bit 1, Temps 3e) : réveille le thread `SQPOLL`
100/// endormi (posé quand `IORING_SQ_NEED_WAKEUP` est vu).
101pub(crate) const IORING_ENTER_SQ_WAKEUP: u32 = 1 << 1;
102/// `IORING_ENTER_EXT_ARG` : `arg` pointe une `io_uring_getevents_arg` (timeout).
103pub(crate) const IORING_ENTER_EXT_ARG: u32 = 1 << 3;
104
105// ─────────────────────────────────────────────────────────────────────────
106// Setup flags appliqués via méthodes du builder (axe A, hors `SetupFlags`)
107// ─────────────────────────────────────────────────────────────────────────
108
109/// `IORING_SETUP_CQSIZE` : taille de CQ explicite (`with_completion_queue_entries`).
110pub(crate) const IORING_SETUP_CQSIZE: u32 = 1 << 3;
111/// `IORING_SETUP_ATTACH_WQ` : partage du pool io-wq (`attach_work_queue`).
112pub(crate) const IORING_SETUP_ATTACH_WQ: u32 = 1 << 5;
113
114// ─────────────────────────────────────────────────────────────────────────
115// Register opcodes (axe C) utilisés au Temps 1
116// ─────────────────────────────────────────────────────────────────────────
117
118/// `IORING_REGISTER_PROBE` (8) : introspection des opcodes supportés (§9).
119pub(crate) const IORING_REGISTER_PROBE: u32 = 8;
120/// `IORING_REGISTER_RESTRICTIONS` (11) : applique une liste blanche de
121/// restrictions (S3, Temps 3f) ; possible **uniquement** sur un ring `R_DISABLED`.
122pub(crate) const IORING_REGISTER_RESTRICTIONS: u32 = 11;
123/// `IORING_REGISTER_ENABLE_RINGS` (12) : active un ring `R_DISABLED` (§5.2).
124pub(crate) const IORING_REGISTER_ENABLE_RINGS: u32 = 12;
125/// `IORING_REGISTER_SYNC_CANCEL` (24) : annulation synchrone (§8, S2).
126pub(crate) const IORING_REGISTER_SYNC_CANCEL: u32 = 24;
127
128// ─────────────────────────────────────────────────────────────────────────
129// Register opcodes du Temps 3a (axe C, registration — 21 opcodes, spec §10).
130// Seules les variantes `*2` taguées sont exposées (legacy 0/2/6 évacuées).
131// ─────────────────────────────────────────────────────────────────────────
132
133/// `IORING_UNREGISTER_BUFFERS` (1) : désenregistre les buffers (`RegisteredBuffers`).
134pub(crate) const IORING_UNREGISTER_BUFFERS: u32 = 1;
135/// `IORING_UNREGISTER_FILES` (3) : désenregistre la table de FD fixes.
136pub(crate) const IORING_UNREGISTER_FILES: u32 = 3;
137/// `IORING_REGISTER_EVENTFD` (4) : lie un eventfd aux complétions.
138pub(crate) const IORING_REGISTER_EVENTFD: u32 = 4;
139/// `IORING_UNREGISTER_EVENTFD` (5).
140pub(crate) const IORING_UNREGISTER_EVENTFD: u32 = 5;
141/// `IORING_REGISTER_EVENTFD_ASYNC` (7) : eventfd notifié seulement pour les
142/// complétions traitées en asynchrone.
143pub(crate) const IORING_REGISTER_EVENTFD_ASYNC: u32 = 7;
144/// `IORING_REGISTER_PERSONALITY` (9) : enregistre les credentials courants.
145pub(crate) const IORING_REGISTER_PERSONALITY: u32 = 9;
146/// `IORING_UNREGISTER_PERSONALITY` (10).
147pub(crate) const IORING_UNREGISTER_PERSONALITY: u32 = 10;
148/// `IORING_REGISTER_FILES2` (13) : table de FD fixes (sparse/tags).
149pub(crate) const IORING_REGISTER_FILES2: u32 = 13;
150/// `IORING_REGISTER_FILES_UPDATE2` (14) : place/remplace des FD dans la table.
151pub(crate) const IORING_REGISTER_FILES_UPDATE2: u32 = 14;
152/// `IORING_REGISTER_BUFFERS2` (15) : buffers enregistrés (sparse/tags).
153pub(crate) const IORING_REGISTER_BUFFERS2: u32 = 15;
154/// `IORING_REGISTER_BUFFERS_UPDATE` (16) : remplace un buffer enregistré.
155pub(crate) const IORING_REGISTER_BUFFERS_UPDATE: u32 = 16;
156/// `IORING_REGISTER_IOWQ_AFF` (17) : affinité CPU des workers io-wq.
157pub(crate) const IORING_REGISTER_IOWQ_AFF: u32 = 17;
158/// `IORING_UNREGISTER_IOWQ_AFF` (18).
159pub(crate) const IORING_UNREGISTER_IOWQ_AFF: u32 = 18;
160/// `IORING_REGISTER_IOWQ_MAX_WORKERS` (19) : plafonds de workers bornés/non-bornés.
161pub(crate) const IORING_REGISTER_IOWQ_MAX_WORKERS: u32 = 19;
162/// `IORING_REGISTER_RING_FDS` (20) : enregistre le fd du ring (enter sans
163/// résolution de FD).
164pub(crate) const IORING_REGISTER_RING_FDS: u32 = 20;
165/// `IORING_UNREGISTER_RING_FDS` (21).
166pub(crate) const IORING_UNREGISTER_RING_FDS: u32 = 21;
167/// `IORING_REGISTER_FILE_ALLOC_RANGE` (25) : borne la plage d'auto-allocation
168/// des slots de FD fixes.
169pub(crate) const IORING_REGISTER_FILE_ALLOC_RANGE: u32 = 25;
170/// `IORING_REGISTER_NAPI` (27) : busy-poll NAPI.
171///
172/// **Écart headers** : absent de `/usr/include/linux/io_uring.h` installé
173/// (`IORING_REGISTER_LAST == 27` y borne l'énumération à 26/`PBUF_STATUS`).
174/// Valeur reprise de l'uapi **6.12** amont (cible de la spec) ; validée à
175/// l'exécution sur le kernel courant (≥ 6.12).
176pub(crate) const IORING_REGISTER_NAPI: u32 = 27;
177/// `IORING_UNREGISTER_NAPI` (28). Même écart headers que [`IORING_REGISTER_NAPI`].
178pub(crate) const IORING_UNREGISTER_NAPI: u32 = 28;
179/// `IORING_REGISTER_CLOCK` (29) : source d'horloge des timeouts du ring. Même
180/// écart headers que [`IORING_REGISTER_NAPI`] (uapi 6.12 amont).
181pub(crate) const IORING_REGISTER_CLOCK: u32 = 29;
182/// `IORING_REGISTER_CLONE_BUFFERS` (30) : clone les buffers d'un autre ring.
183/// Même écart headers que [`IORING_REGISTER_NAPI`] (uapi 6.12 amont).
184pub(crate) const IORING_REGISTER_CLONE_BUFFERS: u32 = 30;
185
186/// `IORING_RSRC_REGISTER_SPARSE` (bit 0) : enregistre un espace **sparse**
187/// (slots vides à remplir plus tard), au lieu d'un tableau de FD `-1`.
188pub(crate) const IORING_RSRC_REGISTER_SPARSE: u32 = 1 << 0;
189/// `IORING_FILE_INDEX_ALLOC` (`~0U`) : sentinelle kernel « auto-allouer un slot
190/// libre » posée dans `file_index`. **Jamais exposée** : la façade la dérive de
191/// [`FixedSlotTarget::Alloc`](super::registration::FixedSlotTarget) (ADR-021).
192pub(crate) const IORING_FILE_INDEX_ALLOC: u32 = u32::MAX;
193/// `IORING_ENTER_REGISTERED_RING` (bit 4) : `fd` d'`enter` est un index de ring
194/// enregistré, pas un FD ordinaire (posé une fois le ring fd enregistré).
195pub(crate) const IORING_ENTER_REGISTERED_RING: u32 = 1 << 4;
196
197/// `IOSQE_FIXED_FILE` (bit 0) : le `fd` du SQE est un **index de slot fixe**
198/// (`FixedSlot`), pas un FD ordinaire.
199pub(crate) const IOSQE_FIXED_FILE: u8 = 1 << 0;
200/// `IORING_OP_FIXED_FD_INSTALL` (54) : matérialise un FD fixe (direct) en FD
201/// **ordinaire** rendu dans `cqe->res`.
202pub(crate) const IORING_OP_FIXED_FD_INSTALL: u8 = 54;
203
204/// `IO_WQ_BOUND` (0) : index de la catégorie de workers io-wq **bornés** (I/O
205/// disque) dans le tableau `IOWQ_MAX_WORKERS`.
206pub(crate) const IO_WQ_BOUND: usize = 0;
207/// `IO_WQ_UNBOUND` (1) : catégorie **non-bornée** (réseau, attentes longues).
208pub(crate) const IO_WQ_UNBOUND: usize = 1;
209
210/// `CLOCK_REALTIME` (0) : horloge temps réel (`ClockSource::Realtime`).
211pub(crate) const CLOCK_REALTIME: u32 = 0;
212/// `CLOCK_MONOTONIC` (1) : horloge monotone (`ClockSource::Monotonic`).
213pub(crate) const CLOCK_MONOTONIC: u32 = 1;
214/// `CLOCK_BOOTTIME` (7) : monotone incluant le temps de veille
215/// (`ClockSource::Boottime`).
216pub(crate) const CLOCK_BOOTTIME: u32 = 7;
217
218// ─────────────────────────────────────────────────────────────────────────
219// Buffers fournis ring-mapped (Temps 3b) — register opcodes + flags
220// ─────────────────────────────────────────────────────────────────────────
221
222/// `IORING_REGISTER_PBUF_RING` (22) : enregistre un groupe de buffers fournis
223/// *ring-mapped* (`ProvidedBufferRing`).
224pub(crate) const IORING_REGISTER_PBUF_RING: u32 = 22;
225/// `IORING_UNREGISTER_PBUF_RING` (23) : désenregistre un groupe.
226pub(crate) const IORING_UNREGISTER_PBUF_RING: u32 = 23;
227/// `IORING_REGISTER_PBUF_STATUS` (26) : tête courante d'un groupe (diagnostic).
228pub(crate) const IORING_REGISTER_PBUF_STATUS: u32 = 26;
229
230/// `IOSQE_BUFFER_SELECT` (bit 5) : l'op sélectionne **automatiquement** un buffer
231/// dans le groupe `buf_group` (pas de buffer attaché au SQE).
232pub(crate) const IOSQE_BUFFER_SELECT: u8 = 1 << 5;
233
234// `IORING_CQE_F_BUFFER` / `IORING_CQE_BUFFER_SHIFT` (id de buffer dans les 16
235// bits hauts de `cqe->flags`) sont exposés typés via `CompletionFlags::BUFFER`
236// et `Completion::buffer_id` (Temps 1) — pas de constante brute dédiée ici.
237
238/// `IOU_PBUF_RING_MMAP` (1) : le kernel alloue la mémoire de l'anneau de
239/// descripteurs (mode `kernel_mmap`), mmappée ensuite par la façade.
240pub(crate) const IOU_PBUF_RING_MMAP: u16 = 1;
241/// `IOU_PBUF_RING_INC` (2) : consommation **incrémentale** (un buffer peut servir
242/// plusieurs complétions, `CQE_F_BUF_MORE`).
243///
244/// **Écart headers** : absent des headers installés (ajout uapi 6.11/6.12) ;
245/// valeur reprise de l'amont, validée au runtime (kernel ≥ 6.12).
246pub(crate) const IOU_PBUF_RING_INC: u16 = 2;
247
248/// `IORING_RECVSEND_BUNDLE` (bit 4 de `ioprio`) : un seul recv/send consomme
249/// **plusieurs** buffers contigus du groupe (`FEAT_RECVSEND_BUNDLE`).
250///
251/// **Écart headers** : absent des headers installés (ajout uapi 6.10/6.12) ;
252/// valeur reprise de l'amont, validée au runtime.
253pub(crate) const IORING_RECVSEND_BUNDLE: u16 = 1 << 4;
254
255// `IORING_OP_RECV` (Temps 2b) et `IORING_OP_READ` (Temps 2a) sont réutilisés ici
256// avec `IOSQE_BUFFER_SELECT` (pas de nouvel opcode).
257
258/// Taille d'un `struct io_uring_buf` (descripteur de buffer dans l'anneau) :
259/// 16 octets (figé par l'assert de layout). L'anneau en contient `count`.
260pub(crate) const IO_URING_BUF_SIZE: usize = 16;
261/// Offset (octets) du champ `tail` de `struct io_uring_buf_ring` : il **recouvre**
262/// le `resv` (offset 14) du premier `io_uring_buf` (union kernel). C'est ce mot
263/// `u16` (atomique, store-release) que la façade avance pour publier des buffers.
264pub(crate) const IO_URING_BUF_RING_TAIL_OFFSET: usize = 14;
265
266// ─────────────────────────────────────────────────────────────────────────
267// Opcodes d'opération (axe B) et flags par-SQE (axe D) utilisés au Temps 1
268// ─────────────────────────────────────────────────────────────────────────
269
270/// `IORING_OP_NOP` (0) : no-op, première op de validation du cœur.
271pub(crate) const IORING_OP_NOP: u8 = 0;
272
273// Opcodes filesystem du Temps 2a (axe B, uapi 6.12). Cf.
274// `docs/specs/layer-0/io-uring-2a-filesystem.md` §10.
275/// `IORING_OP_READV` (1).
276pub(crate) const IORING_OP_READV: u8 = 1;
277/// `IORING_OP_WRITEV` (2).
278pub(crate) const IORING_OP_WRITEV: u8 = 2;
279/// `IORING_OP_FSYNC` (3).
280pub(crate) const IORING_OP_FSYNC: u8 = 3;
281/// `IORING_OP_READ_FIXED` (4, Temps 3a) : lecture dans un **buffer enregistré**
282/// (`RegisteredBufferSlice`), `buf_index` désignant le buffer.
283pub(crate) const IORING_OP_READ_FIXED: u8 = 4;
284/// `IORING_OP_WRITE_FIXED` (5, Temps 3a) : écriture depuis un buffer enregistré.
285pub(crate) const IORING_OP_WRITE_FIXED: u8 = 5;
286/// `IORING_OP_SYNC_FILE_RANGE` (8).
287pub(crate) const IORING_OP_SYNC_FILE_RANGE: u8 = 8;
288/// `IORING_OP_FALLOCATE` (17).
289pub(crate) const IORING_OP_FALLOCATE: u8 = 17;
290/// `IORING_OP_CLOSE` (19).
291pub(crate) const IORING_OP_CLOSE: u8 = 19;
292/// `IORING_OP_STATX` (21).
293pub(crate) const IORING_OP_STATX: u8 = 21;
294/// `IORING_OP_READ` (22).
295pub(crate) const IORING_OP_READ: u8 = 22;
296/// `IORING_OP_WRITE` (23).
297pub(crate) const IORING_OP_WRITE: u8 = 23;
298/// `IORING_OP_FADVISE` (24).
299pub(crate) const IORING_OP_FADVISE: u8 = 24;
300/// `IORING_OP_MADVISE` (25). Conseil mémoire asynchrone sur une `MmapRegion`
301/// (handle de vivacité garé en S1 ; cf. `family-mem-mmap-region.md`).
302pub(crate) const IORING_OP_MADVISE: u8 = 25;
303/// `IORING_OP_OPENAT2` (28).
304pub(crate) const IORING_OP_OPENAT2: u8 = 28;
305/// `IORING_OP_SPLICE` (30).
306pub(crate) const IORING_OP_SPLICE: u8 = 30;
307/// `IORING_OP_TEE` (33).
308pub(crate) const IORING_OP_TEE: u8 = 33;
309/// `IORING_OP_RENAMEAT` (35, sémantique `renameat2`).
310pub(crate) const IORING_OP_RENAMEAT: u8 = 35;
311/// `IORING_OP_UNLINKAT` (36).
312pub(crate) const IORING_OP_UNLINKAT: u8 = 36;
313/// `IORING_OP_MKDIRAT` (37).
314pub(crate) const IORING_OP_MKDIRAT: u8 = 37;
315/// `IORING_OP_SYMLINKAT` (38).
316pub(crate) const IORING_OP_SYMLINKAT: u8 = 38;
317/// `IORING_OP_LINKAT` (39).
318pub(crate) const IORING_OP_LINKAT: u8 = 39;
319/// `IORING_OP_FSETXATTR` (41).
320pub(crate) const IORING_OP_FSETXATTR: u8 = 41;
321/// `IORING_OP_SETXATTR` (42).
322pub(crate) const IORING_OP_SETXATTR: u8 = 42;
323/// `IORING_OP_FGETXATTR` (43).
324pub(crate) const IORING_OP_FGETXATTR: u8 = 43;
325/// `IORING_OP_GETXATTR` (44).
326pub(crate) const IORING_OP_GETXATTR: u8 = 44;
327/// `IORING_OP_FTRUNCATE` (55).
328pub(crate) const IORING_OP_FTRUNCATE: u8 = 55;
329
330// Opcodes réseau du Temps 2b (axe B, uapi 6.12). Cf.
331// `docs/specs/layer-0/io-uring-2b-network.md` §6.
332/// `IORING_OP_SENDMSG` (9).
333pub(crate) const IORING_OP_SENDMSG: u8 = 9;
334/// `IORING_OP_RECVMSG` (10).
335pub(crate) const IORING_OP_RECVMSG: u8 = 10;
336/// `IORING_OP_ACCEPT` (13).
337pub(crate) const IORING_OP_ACCEPT: u8 = 13;
338/// `IORING_OP_CONNECT` (16).
339pub(crate) const IORING_OP_CONNECT: u8 = 16;
340/// `IORING_OP_SEND` (26).
341pub(crate) const IORING_OP_SEND: u8 = 26;
342/// `IORING_OP_RECV` (27).
343pub(crate) const IORING_OP_RECV: u8 = 27;
344/// `IORING_OP_SHUTDOWN` (34).
345pub(crate) const IORING_OP_SHUTDOWN: u8 = 34;
346/// `IORING_OP_SOCKET` (45).
347pub(crate) const IORING_OP_SOCKET: u8 = 45;
348/// `IORING_OP_SEND_ZC` (47).
349pub(crate) const IORING_OP_SEND_ZC: u8 = 47;
350/// `IORING_OP_SENDMSG_ZC` (48).
351pub(crate) const IORING_OP_SENDMSG_ZC: u8 = 48;
352/// `IORING_OP_BIND` (56, ajouté en 6.11).
353pub(crate) const IORING_OP_BIND: u8 = 56;
354/// `IORING_OP_LISTEN` (57, ajouté en 6.11).
355pub(crate) const IORING_OP_LISTEN: u8 = 57;
356
357// Opcodes async-spécifiques du Temps 2c (axe B, uapi 6.12). Cf.
358// `docs/specs/layer-0/io-uring-2c-async.md` §8.
359/// `IORING_OP_POLL_ADD` (6).
360pub(crate) const IORING_OP_POLL_ADD: u8 = 6;
361/// `IORING_OP_POLL_REMOVE` (7) — sert aussi à `POLL_UPDATE`.
362pub(crate) const IORING_OP_POLL_REMOVE: u8 = 7;
363/// `IORING_OP_TIMEOUT` (11).
364pub(crate) const IORING_OP_TIMEOUT: u8 = 11;
365/// `IORING_OP_LINK_TIMEOUT` (15, Temps 3c) : borne dans le temps le **maillon
366/// précédent** d'une chaîne liée (émis exclusivement via `LinkedChainBuilder`).
367pub(crate) const IORING_OP_LINK_TIMEOUT: u8 = 15;
368/// `IORING_OP_READ_MULTISHOT` (49, Temps 3d) : lecture **multishot** (flux de
369/// complétions) sur buffers fournis (opcode dédié, pas de drapeau d'op séparé).
370pub(crate) const IORING_OP_READ_MULTISHOT: u8 = 49;
371
372/// `IORING_ACCEPT_MULTISHOT` (bit 0 de `ioprio`, Temps 3d) : `accept` multishot
373/// (un SQE accepte en continu).
374pub(crate) const IORING_ACCEPT_MULTISHOT: u16 = 1 << 0;
375/// `IORING_RECV_MULTISHOT` (bit 1 de `ioprio`, Temps 3d) : `recv` multishot
376/// (un SQE reçoit en continu, buffers fournis).
377pub(crate) const IORING_RECV_MULTISHOT: u16 = 1 << 1;
378/// `IORING_POLL_ADD_MULTI` (bit 0 de `len`, Temps 3d) : `poll_add` multishot
379/// (une complétion par transition d'état surveillée).
380pub(crate) const IORING_POLL_ADD_MULTI: u32 = 1 << 0;
381/// `IORING_TIMEOUT_MULTISHOT` (bit 6 de `timeout_flags`, Temps 3d) : timer
382/// répétitif (une complétion par tick).
383pub(crate) const IORING_TIMEOUT_MULTISHOT: u32 = 1 << 6;
384/// `IORING_OP_TIMEOUT_REMOVE` (12) — sert aussi à `TIMEOUT_UPDATE`.
385pub(crate) const IORING_OP_TIMEOUT_REMOVE: u8 = 12;
386/// `IORING_OP_ASYNC_CANCEL` (14).
387pub(crate) const IORING_OP_ASYNC_CANCEL: u8 = 14;
388/// `IORING_OP_EPOLL_CTL` (29).
389pub(crate) const IORING_OP_EPOLL_CTL: u8 = 29;
390/// `IORING_OP_MSG_RING` (40).
391pub(crate) const IORING_OP_MSG_RING: u8 = 40;
392/// `IORING_OP_WAITID` (50).
393pub(crate) const IORING_OP_WAITID: u8 = 50;
394
395// Opcode + constantes du Temps 2d (`URING_CMD`, passthrough). Cf.
396// `docs/specs/layer-0/io-uring-2d-cmd.md` et `<linux/io_uring.h>`.
397/// `IORING_OP_URING_CMD` (46) : passthrough vers le gestionnaire `uring_cmd` du
398/// fichier visé (socket, NVMe, ublk…).
399pub(crate) const IORING_OP_URING_CMD: u8 = 46;
400/// `IORING_URING_CMD_FIXED` (bit 0) : utiliser un buffer enregistré ; posé dans
401/// `op_flags` (union `uring_cmd_flags`). Seul flag en 6.12.
402pub(crate) const IORING_URING_CMD_FIXED: u32 = 1 << 0;
403/// `SOCKET_URING_OP_SIOCINQ` (0) : octets en file de réception (cmd_op).
404pub(crate) const SOCKET_URING_OP_SIOCINQ: u32 = 0;
405/// `SOCKET_URING_OP_SIOCOUTQ` (1) : octets en file d'émission.
406pub(crate) const SOCKET_URING_OP_SIOCOUTQ: u32 = 1;
407/// `SOCKET_URING_OP_GETSOCKOPT` (2).
408pub(crate) const SOCKET_URING_OP_GETSOCKOPT: u32 = 2;
409/// `SOCKET_URING_OP_SETSOCKOPT` (3).
410pub(crate) const SOCKET_URING_OP_SETSOCKOPT: u32 = 3;
411/// Offset (octets) du début de la zone `cmd[]` dans le SQE (`addr3`/`__pad2`,
412/// puis l'extension `SQE128`). Capacité = `sqe_size - CMD_ZONE_OFFSET`.
413pub(crate) const CMD_ZONE_OFFSET: usize = 48;
414/// `IORING_OP_FUTEX_WAIT` (51). Attente sur un mot futex `u32` d'une
415/// `MmapRegion` (handle de vivacité garé en S1).
416pub(crate) const IORING_OP_FUTEX_WAIT: u8 = 51;
417/// `IORING_OP_FUTEX_WAKE` (52). Réveil de waiters sur un mot futex.
418pub(crate) const IORING_OP_FUTEX_WAKE: u8 = 52;
419/// `IORING_OP_FUTEX_WAITV` (53). Attente sur **plusieurs** mots futex.
420pub(crate) const IORING_OP_FUTEX_WAITV: u8 = 53;
421
422/// `FUTEX2_SIZE_U32` (`0x02`) : le mot futex fait 32 bits. Imposé par les
423/// façades `submit_futex_*` (un mot futex de `MmapRegion` est un `AtomicU32`).
424/// Posé dans `sqe->fd` pour `WAIT`/`WAKE`, et dans `futex_waitv.flags` pour
425/// `WAITV`. Cf. `<linux/futex.h>`.
426pub(crate) const FUTEX2_SIZE_U32: u32 = 0x02;
427
428/// `FUTEX_WAITV_MAX` (128) : nombre maximal d'attendus d'un `futex_waitv`
429/// (`<linux/futex.h>`). Au-delà, le kernel rend `EINVAL` — on valide en amont.
430pub(crate) const FUTEX_WAITV_MAX: usize = 128;
431
432/// `IORING_NOP_INJECT_RESULT` : `submit_nop_with_result` injecte `len` comme
433/// `res` du CQE (`nop_flags`, posé dans `op_flags`).
434pub(crate) const IORING_NOP_INJECT_RESULT: u32 = 1 << 0;
435/// `IORING_TIMEOUT_UPDATE` : `submit_timeout_update` (dans `timeout_flags`).
436pub(crate) const IORING_TIMEOUT_UPDATE: u32 = 1 << 1;
437/// `IORING_POLL_UPDATE_EVENTS` : `submit_poll_update` modifie les événements.
438pub(crate) const IORING_POLL_UPDATE_EVENTS: u32 = 1 << 1;
439/// `IORING_MSG_DATA` (0) : sous-commande « poster un CQE » de `MSG_RING`.
440pub(crate) const IORING_MSG_DATA: u64 = 0;
441
442// `idtype` de `waitid(2)` (cf. `linux/wait.h`), pour `IORING_OP_WAITID`.
443/// `P_ALL` : n'importe quel enfant.
444pub(crate) const P_ALL: i32 = 0;
445/// `P_PID` : un PID précis.
446pub(crate) const P_PID: i32 = 1;
447/// `P_PGID` : un groupe de processus.
448pub(crate) const P_PGID: i32 = 2;
449/// `P_PIDFD` : un pidfd.
450pub(crate) const P_PIDFD: i32 = 3;
451
452/// `SOCK_CLOEXEC` (asm-generic) : à OR-er dans le `type` de `socket`.
453pub(crate) const SOCK_CLOEXEC: i32 = 0x0008_0000;
454/// `IORING_SEND_ZC_REPORT_USAGE` : rapport d'usage zero-copy (dans `ioprio`).
455pub(crate) const IORING_SEND_ZC_REPORT_USAGE: u16 = 1 << 0;
456/// `IORING_NOTIF_USAGE_ZC_COPIED` : bit du `res` de la CQE NOTIF indiquant que
457/// le kernel a dû **copier** (pas de vrai zero-copy) quand `REPORT_USAGE` actif.
458pub(crate) const IORING_NOTIF_USAGE_ZC_COPIED: i32 = i32::MIN; // bit 31 (0x8000_0000)
459
460/// `IORING_FSYNC_DATASYNC` : bit `fsync_flags` sélectionnant `fdatasync`.
461pub(crate) const IORING_FSYNC_DATASYNC: u32 = 1 << 0;
462/// `O_CLOEXEC` (asm-generic, x86_64/aarch64) : ajouté d'office aux ouvertures
463/// (cohérent avec le wrapper synchrone `openat2`).
464pub(crate) const O_CLOEXEC: u64 = 0x0008_0000;
465/// `AT_FDCWD` : descripteur sentinelle « répertoire courant » ([`DirFd::Cwd`]).
466pub(crate) const AT_FDCWD: i32 = -100;
467/// Nombre maximal d'`iovec` par opération vectorisée (`IOV_MAX`).
468pub(crate) const IOV_MAX: usize = 1024;
469
470/// `IOSQE_IO_DRAIN` : draine les opérations précédentes avant celle-ci.
471pub(crate) const IOSQE_IO_DRAIN: u8 = 1 << 1;
472/// `IOSQE_IO_LINK` (bit 2, Temps 3c) : lie le SQE **suivant** à celui-ci (chaîne
473/// **soft** — une erreur de complétion rompt la chaîne, *short read inclus*).
474pub(crate) const IOSQE_IO_LINK: u8 = 1 << 2;
475/// `IOSQE_IO_HARDLINK` (bit 3, Temps 3c) : lien **hard** — une erreur de
476/// complétion **ne rompt pas** la chaîne (suivants exécutés). Implique l'ordre.
477pub(crate) const IOSQE_IO_HARDLINK: u8 = 1 << 3;
478/// `IOSQE_ASYNC` : force l'exécution asynchrone (io-wq).
479pub(crate) const IOSQE_ASYNC: u8 = 1 << 4;
480/// `IOSQE_CQE_SKIP_SUCCESS` : pas de CQE en cas de succès.
481pub(crate) const IOSQE_CQE_SKIP_SUCCESS: u8 = 1 << 6;
482
483// ─────────────────────────────────────────────────────────────────────────
484// Flags d'annulation `IORING_ASYNC_CANCEL_*` (sync_cancel, S2)
485// ─────────────────────────────────────────────────────────────────────────
486
487/// `IORING_ASYNC_CANCEL_FD` : matcher sur le descripteur de fichier.
488pub(crate) const IORING_ASYNC_CANCEL_FD: u32 = 1 << 1;
489/// `IORING_ASYNC_CANCEL_ANY` : matcher n'importe quelle requête (quiescence).
490pub(crate) const IORING_ASYNC_CANCEL_ANY: u32 = 1 << 2;
491/// `IORING_ASYNC_CANCEL_OP` : matcher sur l'opcode.
492pub(crate) const IORING_ASYNC_CANCEL_OP: u32 = 1 << 5;
493
494/// Nombre d'opcodes interrogés par `IORING_REGISTER_PROBE` (couvre 0..=255).
495pub(crate) const IO_URING_PROBE_OPS: u32 = 256;
496/// `IO_URING_OP_SUPPORTED` : bit de `io_uring_probe_op.flags` indiquant que
497/// l'opcode est supporté par le kernel courant.
498pub(crate) const IO_URING_OP_SUPPORTED: u16 = 1 << 0;
499
500/// Miroir de `struct io_uring_probe_op` (entrée du probe par opcode).
501#[repr(C)]
502#[derive(Debug, Clone, Copy, Default)]
503pub(crate) struct IoUringProbeOp {
504    pub(crate) op: u8,
505    pub(crate) resv: u8,
506    pub(crate) flags: u16,
507    pub(crate) resv2: u32,
508}
509
510/// Miroir de `struct io_uring_probe` (en-tête + tableau d'opcodes sondés).
511#[repr(C)]
512#[derive(Debug, Clone, Copy)]
513pub(crate) struct IoUringProbe {
514    pub(crate) last_op: u8,
515    pub(crate) ops_len: u8,
516    pub(crate) resv: u16,
517    pub(crate) resv2: [u32; 3],
518    pub(crate) ops: [IoUringProbeOp; 256],
519}
520
521/// Miroir de `struct __kernel_timespec` (attente avec timeout via `EXT_ARG`).
522#[repr(C)]
523#[derive(Debug, Clone, Copy, Default)]
524pub(crate) struct KernelTimespec {
525    pub(crate) tv_sec: i64,
526    pub(crate) tv_nsec: i64,
527}
528
529/// Miroir de `struct futex_waitv` (`<linux/futex.h>`), lu par le kernel pour
530/// `IORING_OP_FUTEX_WAITV`. Un élément par mot futex attendu. Le kernel lit
531/// le tableau de façon **asynchrone** jusqu'à la complétion : il est garé en
532/// S1 ([`crate::io_uring::owned::OwnedOp::FutexWaitv`]) avec les gardes de
533/// vivacité des régions désignées par `uaddr`.
534#[repr(C)]
535#[derive(Debug, Clone, Copy)]
536pub(crate) struct FutexWaitvRaw {
537    /// Valeur attendue au mot.
538    pub(crate) val: u64,
539    /// Adresse userspace du mot futex (dans une `MmapRegion` maintenue vivante).
540    pub(crate) uaddr: u64,
541    /// Drapeaux `FUTEX2_*` du mot (taille + `PRIVATE` éventuel).
542    pub(crate) flags: u32,
543    /// Réservé (`__reserved`), **doit** être nul (sinon `EINVAL` kernel).
544    pub(crate) reserved: u32,
545}
546
547// ─────────────────────────────────────────────────────────────────────────
548// Conversion du retour syscall négatif en Errno
549// ─────────────────────────────────────────────────────────────────────────
550
551/// Convertit un retour syscall **strictement négatif** (`-errno`) en [`Errno`].
552///
553/// # Panics
554///
555/// Panique si `ret >= 0` (ce n'est pas un code d'erreur) ou si `-ret` sort de
556/// la plage errno valide (`1..=4095`) — invariants garantis par l'appelant qui
557/// teste `ret < 0` avant d'appeler. Identique au helper des autres familles.
558pub(crate) fn errno_from_negative_syscall_ret(ret: i64) -> Errno {
559    debug_assert!(ret < 0, "errno_from_negative_syscall_ret: ret < 0 requis");
560    // -ret est dans 1..=4095 pour un errno kernel valide.
561    let code = ret
562        .checked_neg()
563        .and_then(|v| i32::try_from(v).ok())
564        .filter(|v| (1..=4095).contains(v))
565        .expect("retour syscall hors plage errno");
566    // SAFETY: `code` vient d'être filtré dans `1..=4095`, donc non nul.
567    Errno::from_nonzero(unsafe { core::num::NonZeroI32::new_unchecked(code) })
568}
569
570// ─────────────────────────────────────────────────────────────────────────
571// Wrappers asm! — x86_64
572// ─────────────────────────────────────────────────────────────────────────
573
574/// `io_uring_setup(entries, params)` — 2 arguments.
575///
576/// # Safety
577///
578/// `params` doit pointer vers une `io_uring_params` valide et accessible en
579/// lecture/écriture pour la durée de l'appel.
580#[cfg(target_arch = "x86_64")]
581#[inline]
582pub(crate) unsafe fn syscall_setup(entries: u32, params: u64) -> i64 {
583    let ret: i64;
584    // SAFETY: `syscall` x86_64 ; l'appelant garantit la validité de `params`.
585    // `entries` est un scalaire, sans mémoire associée.
586    unsafe {
587        core::arch::asm!(
588            "syscall",
589            in("rax") NR_IO_URING_SETUP,
590            in("rdi") u64::from(entries),
591            in("rsi") params,
592            lateout("rax") ret,
593            lateout("rcx") _,
594            lateout("r11") _,
595            options(nostack, preserves_flags),
596        );
597    }
598    ret
599}
600
601/// `io_uring_enter(fd, to_submit, min_complete, flags, arg, argsz)` — 6 args.
602///
603/// # Safety
604///
605/// `fd` doit être un ring fd valide ; si `flags` active `EXT_ARG`, `arg` doit
606/// pointer vers une `io_uring_getevents_arg` valide de taille `argsz`.
607#[cfg(target_arch = "x86_64")]
608#[inline]
609pub(crate) unsafe fn syscall_enter(
610    fd: i32,
611    to_submit: u32,
612    min_complete: u32,
613    flags: u32,
614    arg: u64,
615    argsz: u64,
616) -> i64 {
617    let ret: i64;
618    // SAFETY: `syscall` x86_64 ; l'appelant garantit `fd` ouvert et la
619    // cohérence `arg`/`argsz` vis-à-vis de `flags`.
620    unsafe {
621        core::arch::asm!(
622            "syscall",
623            in("rax") NR_IO_URING_ENTER,
624            in("rdi") i64::from(fd),
625            in("rsi") u64::from(to_submit),
626            in("rdx") u64::from(min_complete),
627            in("r10") u64::from(flags),
628            in("r8") arg,
629            in("r9") argsz,
630            lateout("rax") ret,
631            lateout("rcx") _,
632            lateout("r11") _,
633            options(nostack, preserves_flags),
634        );
635    }
636    ret
637}
638
639/// `io_uring_register(fd, opcode, arg, nr_args)` — 4 arguments.
640///
641/// # Safety
642///
643/// `fd` doit être un ring fd valide ; `arg`/`nr_args` doivent être cohérents
644/// avec `opcode` (structure attendue accessible pour la durée de l'appel).
645#[cfg(target_arch = "x86_64")]
646#[inline]
647pub(crate) unsafe fn syscall_register(fd: i32, opcode: u32, arg: u64, nr_args: u32) -> i64 {
648    let ret: i64;
649    // SAFETY: `syscall` x86_64 ; l'appelant garantit `fd` ouvert et la
650    // cohérence `arg`/`nr_args` vis-à-vis d'`opcode`.
651    unsafe {
652        core::arch::asm!(
653            "syscall",
654            in("rax") NR_IO_URING_REGISTER,
655            in("rdi") i64::from(fd),
656            in("rsi") u64::from(opcode),
657            in("rdx") arg,
658            in("r10") u64::from(nr_args),
659            lateout("rax") ret,
660            lateout("rcx") _,
661            lateout("r11") _,
662            options(nostack, preserves_flags),
663        );
664    }
665    ret
666}
667
668// ─────────────────────────────────────────────────────────────────────────
669// Wrappers asm! — aarch64
670// ─────────────────────────────────────────────────────────────────────────
671
672/// `io_uring_setup(entries, params)` — 2 arguments (aarch64).
673///
674/// # Safety
675///
676/// Voir la variante x86_64.
677#[cfg(target_arch = "aarch64")]
678#[inline]
679pub(crate) unsafe fn syscall_setup(entries: u32, params: u64) -> i64 {
680    let ret: i64;
681    // SAFETY: `svc #0` aarch64 ; l'appelant garantit la validité de `params`.
682    unsafe {
683        core::arch::asm!(
684            "svc #0",
685            in("x8") NR_IO_URING_SETUP,
686            in("x0") u64::from(entries),
687            in("x1") params,
688            lateout("x0") ret,
689            options(nostack, preserves_flags),
690        );
691    }
692    ret
693}
694
695/// `io_uring_enter(...)` — 6 arguments (aarch64).
696///
697/// # Safety
698///
699/// Voir la variante x86_64.
700#[cfg(target_arch = "aarch64")]
701#[inline]
702pub(crate) unsafe fn syscall_enter(
703    fd: i32,
704    to_submit: u32,
705    min_complete: u32,
706    flags: u32,
707    arg: u64,
708    argsz: u64,
709) -> i64 {
710    let ret: i64;
711    // SAFETY: `svc #0` aarch64 ; mêmes préconditions que x86_64.
712    unsafe {
713        core::arch::asm!(
714            "svc #0",
715            in("x8") NR_IO_URING_ENTER,
716            in("x0") i64::from(fd),
717            in("x1") u64::from(to_submit),
718            in("x2") u64::from(min_complete),
719            in("x3") u64::from(flags),
720            in("x4") arg,
721            in("x5") argsz,
722            lateout("x0") ret,
723            options(nostack, preserves_flags),
724        );
725    }
726    ret
727}
728
729/// `io_uring_register(...)` — 4 arguments (aarch64).
730///
731/// # Safety
732///
733/// Voir la variante x86_64.
734#[cfg(target_arch = "aarch64")]
735#[inline]
736pub(crate) unsafe fn syscall_register(fd: i32, opcode: u32, arg: u64, nr_args: u32) -> i64 {
737    let ret: i64;
738    // SAFETY: `svc #0` aarch64 ; mêmes préconditions que x86_64.
739    unsafe {
740        core::arch::asm!(
741            "svc #0",
742            in("x8") NR_IO_URING_REGISTER,
743            in("x0") i64::from(fd),
744            in("x1") u64::from(opcode),
745            in("x2") arg,
746            in("x3") u64::from(nr_args),
747            lateout("x0") ret,
748            options(nostack, preserves_flags),
749        );
750    }
751    ret
752}
753
754// ─────────────────────────────────────────────────────────────────────────
755// Miroirs #[repr(C)] des structures kernel (noms kernel conservés)
756// ─────────────────────────────────────────────────────────────────────────
757
758/// Miroir de `struct io_sqring_offsets` : offsets des champs de l'anneau SQ
759/// dans la zone mmappée à `IORING_OFF_SQ_RING`.
760#[repr(C)]
761#[derive(Debug, Clone, Copy, Default)]
762pub(crate) struct IoSqringOffsets {
763    pub(crate) head: u32,
764    pub(crate) tail: u32,
765    pub(crate) ring_mask: u32,
766    pub(crate) ring_entries: u32,
767    pub(crate) flags: u32,
768    pub(crate) dropped: u32,
769    pub(crate) array: u32,
770    pub(crate) resv1: u32,
771    pub(crate) user_addr: u64,
772}
773
774/// Miroir de `struct io_cqring_offsets` : offsets des champs de l'anneau CQ.
775#[repr(C)]
776#[derive(Debug, Clone, Copy, Default)]
777pub(crate) struct IoCqringOffsets {
778    pub(crate) head: u32,
779    pub(crate) tail: u32,
780    pub(crate) ring_mask: u32,
781    pub(crate) ring_entries: u32,
782    pub(crate) overflow: u32,
783    pub(crate) cqes: u32,
784    pub(crate) flags: u32,
785    pub(crate) resv1: u32,
786    pub(crate) user_addr: u64,
787}
788
789/// Miroir de `struct io_uring_params` : entrées/sorties de `io_uring_setup(2)`.
790#[repr(C)]
791#[derive(Debug, Clone, Copy, Default)]
792pub(crate) struct IoUringParams {
793    pub(crate) sq_entries: u32,
794    pub(crate) cq_entries: u32,
795    pub(crate) flags: u32,
796    pub(crate) sq_thread_cpu: u32,
797    pub(crate) sq_thread_idle: u32,
798    pub(crate) features: u32,
799    pub(crate) wq_fd: u32,
800    pub(crate) resv: [u32; 3],
801    pub(crate) sq_off: IoSqringOffsets,
802    pub(crate) cq_off: IoCqringOffsets,
803}
804
805/// Miroir de `struct io_uring_sqe` (64 octets ; 128 avec `SETUP_SQE128`).
806///
807/// Seuls les champs utiles au cœur sont nommés ; le reste est couvert par les
808/// unions kernel, représentées ici par des champs scalaires de même offset.
809#[repr(C)]
810#[derive(Debug, Clone, Copy, Default)]
811pub(crate) struct IoUringSqe {
812    pub(crate) opcode: u8,
813    pub(crate) flags: u8,
814    pub(crate) ioprio: u16,
815    pub(crate) fd: i32,
816    /// union { off, addr2, cmd_op } : offset / 2ᵉ adresse selon l'op.
817    pub(crate) off_or_addr2: u64,
818    /// union { addr, splice_off_in } : adresse du buffer selon l'op.
819    pub(crate) addr_or_splice_off_in: u64,
820    pub(crate) len: u32,
821    /// union des flags spécifiques à l'op (rw_flags, fsync_flags, …).
822    pub(crate) op_flags: u32,
823    pub(crate) user_data: u64,
824    /// union { buf_index, buf_group } : index/groupe de buffer.
825    pub(crate) buf_index_or_group: u16,
826    pub(crate) personality: u16,
827    /// union { splice_fd_in, file_index, addr_len } selon l'op.
828    pub(crate) splice_fd_in_or_file_index: u32,
829    pub(crate) addr3: u64,
830    pub(crate) pad2: u64,
831}
832
833/// Miroir de `struct open_how` (`openat2(2)`, 24 octets). Lu **de façon
834/// asynchrone** par le kernel pour `IORING_OP_OPENAT2` ⇒ gardé en vie dans le
835/// slot S1 jusqu'à la complétion.
836#[repr(C)]
837#[derive(Debug, Clone, Copy, Default)]
838pub(crate) struct OpenHowRaw {
839    pub(crate) flags: u64,
840    pub(crate) mode: u64,
841    pub(crate) resolve: u64,
842}
843
844/// Miroir de `struct msghdr` (LP64, x86_64/aarch64) pour `SENDMSG`/`RECVMSG`.
845/// Lu (et, pour recvmsg, **réécrit**) de façon asynchrone par le kernel ⇒ gardé
846/// en vie dans le slot S1 jusqu'à la complétion. Les pointeurs (`msg_name`,
847/// `msg_iov`, `msg_control`) désignent des allocations **indépendantes** elles
848/// aussi parquées, donc à adresses stables.
849#[repr(C)]
850#[derive(Debug, Clone, Copy, Default)]
851pub(crate) struct Msghdr {
852    pub(crate) msg_name: u64,
853    pub(crate) msg_namelen: u32,
854    pub(crate) pad1: u32,
855    pub(crate) msg_iov: u64,
856    pub(crate) msg_iovlen: u64,
857    pub(crate) msg_control: u64,
858    pub(crate) msg_controllen: u64,
859    pub(crate) msg_flags: i32,
860    pub(crate) pad2: u32,
861}
862
863/// Miroir de `struct iovec` (`<sys/uio.h>`) : base + longueur d'un segment de
864/// buffer, pour les opérations vectorisées (`READV`/`WRITEV`). Le tableau
865/// d'`iovec` est construit côté Air et **garé dans le slot S1** (il doit rester
866/// valide jusqu'à la complétion ; le kernel le lit de façon asynchrone).
867#[repr(C)]
868#[derive(Debug, Clone, Copy)]
869pub(crate) struct Iovec {
870    pub(crate) iov_base: *mut u8,
871    pub(crate) iov_len: usize,
872}
873
874/// Miroir de `struct io_uring_cqe` (16 octets ; 32 avec `SETUP_CQE32`).
875#[repr(C)]
876#[derive(Debug, Clone, Copy, Default)]
877pub(crate) struct IoUringCqe {
878    pub(crate) user_data: u64,
879    pub(crate) res: i32,
880    pub(crate) flags: u32,
881}
882
883/// Miroir de `struct io_uring_getevents_arg` : argument étendu d'`enter`
884/// (`IORING_ENTER_EXT_ARG`) pour l'attente avec timeout.
885#[repr(C)]
886#[derive(Debug, Clone, Copy, Default)]
887pub(crate) struct IoUringGeteventsArg {
888    pub(crate) sigmask: u64,
889    pub(crate) sigmask_sz: u32,
890    pub(crate) min_wait_usec: u32,
891    pub(crate) ts: u64,
892}
893
894/// Miroir de `struct io_uring_sync_cancel_reg` : argument de
895/// `IORING_REGISTER_SYNC_CANCEL` (brique de la quiescence S2).
896#[repr(C)]
897#[derive(Debug, Clone, Copy, Default)]
898pub(crate) struct IoUringSyncCancelReg {
899    pub(crate) addr: u64,
900    pub(crate) fd: i32,
901    pub(crate) flags: u32,
902    pub(crate) timeout_sec: i64,
903    pub(crate) timeout_nsec: i64,
904    pub(crate) opcode: u8,
905    pub(crate) pad: [u8; 7],
906    pub(crate) pad2: [u64; 3],
907}
908
909/// Miroir de `struct io_uring_rsrc_register` : argument de `REGISTER_FILES2`
910/// (13) et `REGISTER_BUFFERS2` (15). `data` pointe le tableau de ressources
911/// (FD `i32`, ou `io_uring_iovec` pour les buffers), `tags` le tableau de tags
912/// optionnel (`FEAT_RSRC_TAGS`), `nr` le nombre d'entrées. `flags` porte
913/// `IORING_RSRC_REGISTER_SPARSE`.
914#[repr(C)]
915#[derive(Debug, Clone, Copy, Default)]
916pub(crate) struct IoUringRsrcRegister {
917    pub(crate) nr: u32,
918    pub(crate) flags: u32,
919    pub(crate) resv2: u64,
920    pub(crate) data: u64,
921    pub(crate) tags: u64,
922}
923
924/// Miroir de `struct io_uring_rsrc_update` (16 octets) : argument de
925/// `REGISTER_RING_FDS` (20) / `UNREGISTER_RING_FDS` (21). `data` porte le FD du
926/// ring, `offset` l'index (`~0U` = auto à l'enregistrement ; réécrit par le
927/// kernel avec l'index assigné).
928#[repr(C)]
929#[derive(Debug, Clone, Copy, Default)]
930pub(crate) struct IoUringRsrcUpdate {
931    pub(crate) offset: u32,
932    pub(crate) resv: u32,
933    pub(crate) data: u64,
934}
935
936/// Miroir de `struct io_uring_rsrc_update2` : argument de `FILES_UPDATE2` (14)
937/// et `BUFFERS_UPDATE` (16). Met à jour `nr` ressources à partir de `offset`,
938/// `data` pointant le nouveau tableau et `tags` les tags associés.
939#[repr(C)]
940#[derive(Debug, Clone, Copy, Default)]
941pub(crate) struct IoUringRsrcUpdate2 {
942    pub(crate) offset: u32,
943    pub(crate) resv: u32,
944    pub(crate) data: u64,
945    pub(crate) tags: u64,
946    pub(crate) nr: u32,
947    pub(crate) resv2: u32,
948}
949
950/// Miroir de `struct io_uring_file_index_range` : argument de
951/// `REGISTER_FILE_ALLOC_RANGE` (25). Plage `[off, off + len)` des slots
952/// éligibles à l'auto-allocation.
953#[repr(C)]
954#[derive(Debug, Clone, Copy, Default)]
955pub(crate) struct IoUringFileIndexRange {
956    pub(crate) off: u32,
957    pub(crate) len: u32,
958    pub(crate) resv: u64,
959}
960
961/// Miroir de `struct io_uring_clock_register` : argument de `REGISTER_CLOCK`
962/// (29). `clockid` est un `CLOCK_*` POSIX.
963///
964/// **Écart headers** : absent des headers installés (ajout uapi 6.12) ; layout
965/// repris de l'amont (4 × `u32`, 16 octets).
966#[repr(C)]
967#[derive(Debug, Clone, Copy, Default)]
968pub(crate) struct IoUringClockRegister {
969    pub(crate) clockid: u32,
970    pub(crate) resv: [u32; 3],
971}
972
973/// Miroir de `struct io_uring_napi` : argument **bidirectionnel** de
974/// `REGISTER_NAPI` (27) — le kernel y réécrit la configuration précédente.
975/// `busy_poll_to` est un timeout en µs ; `prefer_busy_poll` un booléen.
976///
977/// **Écart headers** : absent des headers installés (ajout uapi 6.12) ; layout
978/// repris de l'amont (`u32` + `u8` + `pad[3]` + `u64`, 16 octets).
979#[repr(C)]
980#[derive(Debug, Clone, Copy, Default)]
981pub(crate) struct IoUringNapi {
982    pub(crate) busy_poll_to: u32,
983    pub(crate) prefer_busy_poll: u8,
984    pub(crate) pad: [u8; 3],
985    pub(crate) resv: u64,
986}
987
988/// Miroir de `struct io_uring_clone_buffers` : argument de `CLONE_BUFFERS`
989/// (30). `src_fd` est le ring **source** ; `flags` réservé (0 en 6.12).
990///
991/// **Écart headers** : absent des headers installés (ajout uapi 6.12) ; layout
992/// 6.12 amont (`src_fd` + `flags` + `pad[6]`, 32 octets ; les champs
993/// `src_off`/`dst_off`/`nr` sont des ajouts **postérieurs** à 6.12).
994#[repr(C)]
995#[derive(Debug, Clone, Copy, Default)]
996pub(crate) struct IoUringCloneBuffers {
997    pub(crate) src_fd: u32,
998    pub(crate) flags: u32,
999    pub(crate) pad: [u32; 6],
1000}
1001
1002/// Miroir de `struct io_uring_buf_reg` (40 octets) : argument de `PBUF_RING`
1003/// (22) / `UNREGISTER_PBUF_RING` (23). `ring_addr` = adresse de l'anneau de
1004/// descripteurs (0 si `IOU_PBUF_RING_MMAP`) ; `ring_entries` = `count`
1005/// (puissance de 2) ; `bgid` = group_id ; `flags` = `IOU_PBUF_RING_*`.
1006#[repr(C)]
1007#[derive(Debug, Clone, Copy, Default)]
1008pub(crate) struct IoUringBufReg {
1009    pub(crate) ring_addr: u64,
1010    pub(crate) ring_entries: u32,
1011    pub(crate) bgid: u16,
1012    pub(crate) flags: u16,
1013    pub(crate) resv: [u64; 3],
1014}
1015
1016/// Miroir de `struct io_uring_buf` (16 octets) : un **descripteur** de buffer
1017/// dans l'anneau (`addr`/`len`/`bid`). `resv` (offset 14) recouvre le `tail` de
1018/// l'anneau pour le **premier** descripteur (cf. [`IO_URING_BUF_RING_TAIL_OFFSET`]).
1019#[repr(C)]
1020#[derive(Debug, Clone, Copy, Default)]
1021pub(crate) struct IoUringBuf {
1022    pub(crate) addr: u64,
1023    pub(crate) len: u32,
1024    pub(crate) bid: u16,
1025    pub(crate) resv: u16,
1026}
1027
1028/// Miroir de `struct io_uring_buf_status` (40 octets) : argument
1029/// **bidirectionnel** de `PBUF_STATUS` (26). `buf_group` en entrée, `head`
1030/// réécrit par le kernel (tête courante du groupe).
1031#[repr(C)]
1032#[derive(Debug, Clone, Copy, Default)]
1033pub(crate) struct IoUringBufStatus {
1034    pub(crate) buf_group: u32,
1035    pub(crate) head: u32,
1036    pub(crate) resv: [u32; 8],
1037}
1038
1039// `enum io_uring_register_restriction_op` (uapi 6.12) : type d'une entrée de
1040// `REGISTER_RESTRICTIONS` (S3, Temps 3f). Place dans le champ `opcode` (u16) de
1041// `io_uring_restriction`.
1042/// `IORING_RESTRICTION_REGISTER_OP` (0) : liste blanche des register-ops.
1043pub(crate) const IORING_RESTRICTION_REGISTER_OP: u16 = 0;
1044/// `IORING_RESTRICTION_SQE_OP` (1) : liste blanche des opcodes de soumission.
1045pub(crate) const IORING_RESTRICTION_SQE_OP: u16 = 1;
1046/// `IORING_RESTRICTION_SQE_FLAGS_ALLOWED` (2) : drapeaux `IOSQE_*` autorisés.
1047pub(crate) const IORING_RESTRICTION_SQE_FLAGS_ALLOWED: u16 = 2;
1048/// `IORING_RESTRICTION_SQE_FLAGS_REQUIRED` (3) : drapeaux `IOSQE_*` imposés à
1049/// chaque SQE.
1050pub(crate) const IORING_RESTRICTION_SQE_FLAGS_REQUIRED: u16 = 3;
1051
1052/// Miroir de `struct io_uring_restriction` (16 octets, uapi 6.12) : une entrée
1053/// de la liste blanche passée à `IORING_REGISTER_RESTRICTIONS` (11). Le champ
1054/// `op_or_flags` est l'union `{ register_op, sqe_op, sqe_flags }` (un `u8` selon
1055/// `opcode`).
1056#[repr(C)]
1057#[derive(Debug, Clone, Copy, Default)]
1058pub(crate) struct IoUringRestriction {
1059    pub(crate) opcode: u16,
1060    pub(crate) op_or_flags: u8,
1061    pub(crate) resv: u8,
1062    pub(crate) resv2: [u32; 3],
1063}
1064
1065// Vérifications de layout (tailles ABI figées par le kernel). Une dérive de
1066// layout (mauvais champ, padding) ferait exploser ces assertions à la
1067// compilation, avant tout appel kernel.
1068const _: () = {
1069    assert!(core::mem::size_of::<IoSqringOffsets>() == 40);
1070    assert!(core::mem::size_of::<IoCqringOffsets>() == 40);
1071    assert!(core::mem::size_of::<IoUringParams>() == 120);
1072    assert!(core::mem::size_of::<IoUringSqe>() == 64);
1073    assert!(core::mem::size_of::<IoUringCqe>() == 16);
1074    assert!(core::mem::size_of::<IoUringGeteventsArg>() == 24);
1075    assert!(core::mem::size_of::<IoUringSyncCancelReg>() == 64);
1076    assert!(core::mem::size_of::<Iovec>() == 16);
1077    assert!(core::mem::size_of::<OpenHowRaw>() == 24);
1078    assert!(core::mem::size_of::<Msghdr>() == 56);
1079    assert!(core::mem::size_of::<IoUringRsrcRegister>() == 32);
1080    assert!(core::mem::size_of::<IoUringRsrcUpdate>() == 16);
1081    assert!(core::mem::size_of::<IoUringRsrcUpdate2>() == 32);
1082    assert!(core::mem::size_of::<IoUringFileIndexRange>() == 16);
1083    assert!(core::mem::size_of::<IoUringClockRegister>() == 16);
1084    assert!(core::mem::size_of::<IoUringNapi>() == 16);
1085    assert!(core::mem::size_of::<IoUringCloneBuffers>() == 32);
1086    assert!(core::mem::size_of::<IoUringBufReg>() == 40);
1087    assert!(core::mem::size_of::<IoUringBuf>() == IO_URING_BUF_SIZE);
1088    assert!(core::mem::size_of::<IoUringBufStatus>() == 40);
1089    assert!(core::mem::size_of::<IoUringRestriction>() == 16);
1090};
1091
1092// ─────────────────────────────────────────────────────────────────────────
1093// Temps 4 — accès brut niveau 1 (surface PUBLIQUE)
1094// ─────────────────────────────────────────────────────────────────────────
1095//
1096// Soupape de sécurité d'ADR-022 (Décision 1) : manipulation directe des SQE/CQE
1097// pour les cas que l'API typée (Temps 1–3f) ne couvre pas. L'ordering d'anneau
1098// reste géré par la façade ; l'`unsafe` ne porte que sur le **contenu** du SQE et
1099// la **validité des buffers** (cf. `IoUring::raw_get_submission_queue_entry`).
1100
1101/// Bit de poids fort du `user_data`, que **toute** opération brute doit poser
1102/// (Temps 4 §5). Le slab S1 borne sa génération à 31 bits et n'utilise donc
1103/// **jamais** ce bit : brut et niveau 2 coexistent sur le même ring sans
1104/// collision. La boucle de complétion de la façade route sur ce bit.
1105pub const RAW_USER_DATA_TAG: u64 = 1 << 63;
1106
1107/// Opcode brut `IORING_OP_*` (couvre aussi les opcodes hors [`IoUringOpcode`],
1108/// p. ex. un opcode apparu après 6.12 et détecté par probe).
1109///
1110/// [`IoUringOpcode`]: super::IoUringOpcode
1111#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1112pub struct RawOpcode(pub u8);
1113
1114/// Miroir exact de `struct io_uring_sqe` (**64 octets**). `#[repr(transparent)]`
1115/// sur le miroir interne `IoUringSqe` ⇒ layout bit-pour-bit garanti (asserté
1116/// statiquement). La zone `cmd[]` étendue de `SETUP_SQE128` (80 o) **n'est pas**
1117/// exposée ici : le passthrough `URING_CMD` passe par la façade typée (Temps 2d).
1118///
1119/// Rempli via les accesseurs typés (pas de calcul d'offset manuel), puis obtenu
1120/// par [`IoUring::raw_get_submission_queue_entry`](super::IoUring::raw_get_submission_queue_entry)
1121/// et publié par `submit()` (Temps 1).
1122#[repr(transparent)]
1123#[derive(Debug, Clone, Copy)]
1124pub struct RawSubmissionQueueEntry(IoUringSqe);
1125
1126impl RawSubmissionQueueEntry {
1127    /// SQE `IORING_OP_NOP` (entièrement zéro-initialisé). **Penser à poser le tag**
1128    /// `RAW_USER_DATA_TAG` sur `user_data` (§5) avant la soumission.
1129    #[must_use]
1130    pub fn nop() -> Self {
1131        Self(IoUringSqe {
1132            opcode: IORING_OP_NOP,
1133            ..IoUringSqe::default()
1134        })
1135    }
1136
1137    /// Fixe l'opcode (`IORING_OP_*`).
1138    pub fn set_opcode(&mut self, opcode: RawOpcode) -> &mut Self {
1139        self.0.opcode = opcode.0;
1140        self
1141    }
1142
1143    /// Fixe les drapeaux `IOSQE_*` (octet `flags`).
1144    pub fn set_flags(&mut self, flags: u8) -> &mut Self {
1145        self.0.flags = flags;
1146        self
1147    }
1148
1149    /// Fixe le `fd` cible (ou l'index de slot fixe si `IOSQE_FIXED_FILE`).
1150    pub fn set_fd(&mut self, fd: i32) -> &mut Self {
1151        self.0.fd = fd;
1152        self
1153    }
1154
1155    /// Fixe `addr` (union `addr`/`splice_off_in` : adresse du buffer principal).
1156    pub fn set_addr(&mut self, addr: u64) -> &mut Self {
1157        self.0.addr_or_splice_off_in = addr;
1158        self
1159    }
1160
1161    /// Fixe `off`/`addr2` (union `off`/`addr2`/`cmd_op` selon l'opcode).
1162    pub fn set_off(&mut self, off: u64) -> &mut Self {
1163        self.0.off_or_addr2 = off;
1164        self
1165    }
1166
1167    /// Fixe `len`.
1168    pub fn set_len(&mut self, len: u32) -> &mut Self {
1169        self.0.len = len;
1170        self
1171    }
1172
1173    /// Fixe les flags spécifiques à l'opcode (`op_flags` : `rw_flags`, etc.).
1174    pub fn set_op_flags(&mut self, op_flags: u32) -> &mut Self {
1175        self.0.op_flags = op_flags;
1176        self
1177    }
1178
1179    /// Fixe `user_data` (**doit** porter [`RAW_USER_DATA_TAG`] pour une op brute, §5).
1180    pub fn set_user_data(&mut self, user_data: u64) -> &mut Self {
1181        self.0.user_data = user_data;
1182        self
1183    }
1184
1185    /// `user_data` courant (pour vérifier le tag §5).
1186    #[must_use]
1187    pub fn user_data(&self) -> u64 {
1188        self.0.user_data
1189    }
1190
1191    /// Opcode courant.
1192    #[must_use]
1193    pub fn opcode(&self) -> RawOpcode {
1194        RawOpcode(self.0.opcode)
1195    }
1196}
1197
1198/// Miroir exact de `struct io_uring_cqe` (**16 octets**). Le `big_cqe[]` (16 o
1199/// supplémentaires) de `SETUP_CQE32` n'est pas exposé. L'interprétation de
1200/// `res`/`flags` est à la charge de l'appelant (selon l'opcode soumis).
1201#[repr(C)]
1202#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1203pub struct RawCompletionQueueEntry {
1204    /// `user_data` de l'op (avec [`RAW_USER_DATA_TAG`] pour une op brute, §5).
1205    pub user_data: u64,
1206    /// Résultat brut : `-errno` si négatif, valeur utile (`fd`, octets…) sinon.
1207    pub res: i32,
1208    /// Flags `IORING_CQE_F_*` (+ id de buffer dans les bits hauts).
1209    pub flags: u32,
1210}
1211
1212// Layout bit-pour-bit contre l'uapi 6.12 (identique x86_64/aarch64, LP64) : toute
1213// dérive casse la compilation.
1214const _: () = {
1215    assert!(core::mem::size_of::<RawSubmissionQueueEntry>() == 64);
1216    assert!(core::mem::align_of::<RawSubmissionQueueEntry>() == 8);
1217    assert!(core::mem::size_of::<RawCompletionQueueEntry>() == 16);
1218    assert!(core::mem::align_of::<RawCompletionQueueEntry>() == 8);
1219    assert!(core::mem::offset_of!(RawCompletionQueueEntry, user_data) == 0);
1220    assert!(core::mem::offset_of!(RawCompletionQueueEntry, res) == 8);
1221    assert!(core::mem::offset_of!(RawCompletionQueueEntry, flags) == 12);
1222    // RawCompletionQueueEntry et le miroir interne IoUringCqe doivent coïncider
1223    // (la façade reinterprète un slot CQE de la mmap en `&RawCompletionQueueEntry`).
1224    assert!(core::mem::size_of::<RawCompletionQueueEntry>() == core::mem::size_of::<IoUringCqe>());
1225};
1226
1227// ─────────────────────────────────────────────────────────────────────────
1228// Tests — Temps 4 (accès brut). Intégration kernel ⇒ `#[cfg_attr(miri, ignore)]`
1229// (io_uring non modélisé par Miri, comme tout le module). Le décode brut total
1230// est exercé sans kernel (fuzz_api) ; la fuzz-cible est `io_uring_4_raw_cqe`.
1231// ─────────────────────────────────────────────────────────────────────────
1232#[cfg(test)]
1233mod tests {
1234    use super::{
1235        IORING_OP_NOP, RAW_USER_DATA_TAG, RawCompletionQueueEntry, RawOpcode,
1236        RawSubmissionQueueEntry,
1237    };
1238    use crate::io_uring::IoUring;
1239    use core::num::NonZeroU32;
1240
1241    fn nz(n: u32) -> NonZeroU32 {
1242        NonZeroU32::new(n).expect("n ≠ 0")
1243    }
1244
1245    // ── Layout (les asserts statiques garantissent ; runtime explicite). ──
1246    #[test]
1247    fn raw_layout_matches_uapi_6_12() {
1248        assert_eq!(core::mem::size_of::<RawSubmissionQueueEntry>(), 64);
1249        assert_eq!(core::mem::align_of::<RawSubmissionQueueEntry>(), 8);
1250        assert_eq!(core::mem::size_of::<RawCompletionQueueEntry>(), 16);
1251        assert_eq!(core::mem::align_of::<RawCompletionQueueEntry>(), 8);
1252    }
1253
1254    // ── Accesseurs typés (sans kernel). ──
1255    #[test]
1256    fn raw_sqe_builders_and_getters() {
1257        let mut sqe = RawSubmissionQueueEntry::nop();
1258        assert_eq!(sqe.opcode(), RawOpcode(IORING_OP_NOP));
1259        sqe.set_opcode(RawOpcode(42))
1260            .set_fd(7)
1261            .set_addr(0x1000)
1262            .set_off(0x20)
1263            .set_len(16)
1264            .set_op_flags(0xABCD)
1265            .set_flags(0)
1266            .set_user_data(0x5 | RAW_USER_DATA_TAG);
1267        assert_eq!(sqe.opcode(), RawOpcode(42));
1268        assert_eq!(sqe.user_data(), 0x5 | RAW_USER_DATA_TAG);
1269        // Dérives Debug/PartialEq/Eq des types bruts.
1270        assert_eq!(RawOpcode(42), RawOpcode(42));
1271        assert!(format!("{:?}", RawOpcode(7)).contains('7'));
1272        let cqe = RawCompletionQueueEntry {
1273            user_data: 1,
1274            res: -5,
1275            flags: 2,
1276        };
1277        assert_eq!(
1278            cqe,
1279            RawCompletionQueueEntry {
1280                user_data: 1,
1281                res: -5,
1282                flags: 2
1283            }
1284        );
1285        assert!(format!("{cqe:?}").contains("res"));
1286    }
1287
1288    // (Le décode brut total — frontière données kernel, Principe 3 — est exercé
1289    // par la fuzz-cible `io_uring_4_raw_cqe` → `fuzz_api::decode_raw_cqe`,
1290    // sous `#[cfg(fuzzing)]`.)
1291
1292    // ── Intégration kernel : NOP brut → raw_peek/raw_advance. ──
1293    #[test]
1294    #[cfg_attr(miri, ignore = "io_uring non supporté par Miri")]
1295    fn raw_nop_round_trips_via_raw_peek_and_advance() {
1296        let mut ring = IoUring::new(nz(8)).expect("ring");
1297        assert!(ring.raw_peek_completion_queue_entry().is_none(), "CQ vide");
1298        let ud = 0xABCD | RAW_USER_DATA_TAG;
1299        // SAFETY: NOP — aucun fd/buffer ; `user_data` tagué (§5) ; SQE rempli
1300        // intégralement avant la publication par `submit_and_wait`.
1301        unsafe {
1302            ring.raw_get_submission_queue_entry()
1303                .expect("emplacement SQ")
1304                .set_opcode(RawOpcode(IORING_OP_NOP))
1305                .set_user_data(ud);
1306        }
1307        assert_eq!(
1308            ring.submission_queue_available(),
1309            ring.submission_queue_capacity() - 1
1310        );
1311        ring.submit_and_wait(1).expect("submit_and_wait");
1312        let cqe = ring.raw_peek_completion_queue_entry().expect("CQE brut");
1313        assert_eq!(cqe.user_data, ud);
1314        assert_eq!(cqe.res, 0, "NOP réussit (res == 0)");
1315        assert_ne!(cqe.user_data & RAW_USER_DATA_TAG, 0, "tag présent");
1316        ring.raw_advance_completion_queue(1);
1317        assert!(ring.raw_peek_completion_queue_entry().is_none(), "consommé");
1318    }
1319
1320    // ── Op brute vs wrapper niveau 2 équivalent (même résultat observable). ──
1321    #[test]
1322    #[cfg_attr(miri, ignore = "io_uring non supporté par Miri")]
1323    fn raw_nop_result_matches_level2_nop() {
1324        let mut r2 = IoUring::new(nz(8)).expect("ring niveau 2");
1325        let tok = r2.submit_nop().expect("nop niveau 2");
1326        r2.submit_and_wait(1).expect("submit l2");
1327        let c = r2.wait_completion().expect("complétion l2");
1328        assert_eq!(c.token(), tok);
1329        c.completed().expect("nop l2 ok");
1330
1331        let mut rr = IoUring::new(nz(8)).expect("ring brut");
1332        // SAFETY: NOP sans buffer ; tagué ; rempli avant publication.
1333        unsafe {
1334            rr.raw_get_submission_queue_entry()
1335                .expect("slot")
1336                .set_opcode(RawOpcode(IORING_OP_NOP))
1337                .set_user_data(RAW_USER_DATA_TAG);
1338        }
1339        rr.submit_and_wait(1).expect("submit brut");
1340        let raw_cqe = rr.raw_peek_completion_queue_entry().expect("cqe brut");
1341        assert_eq!(raw_cqe.res, 0, "même résultat que le wrapper niveau 2");
1342        rr.raw_advance_completion_queue(1);
1343    }
1344
1345    // ── La moisson gérée s'efface devant une complétion brute en tête. ──
1346    #[test]
1347    #[cfg_attr(miri, ignore = "io_uring non supporté par Miri")]
1348    fn managed_drain_yields_to_raw_completion_at_head() {
1349        let mut ring = IoUring::new(nz(8)).expect("ring");
1350        // SAFETY: NOP sans buffer ; tagué (§5) ; rempli avant publication.
1351        unsafe {
1352            ring.raw_get_submission_queue_entry()
1353                .expect("slot")
1354                .set_opcode(RawOpcode(IORING_OP_NOP))
1355                .set_user_data(RAW_USER_DATA_TAG);
1356        }
1357        ring.submit_and_wait(1).expect("submit");
1358        // Seule une complétion **brute** est prête : la moisson gérée renvoie
1359        // `None` **sans la consommer** (court-circuit sur le tag) — elle reste
1360        // disponible pour l'accès brut.
1361        assert!(
1362            ring.try_completion().is_none(),
1363            "la moisson gérée saute la complétion brute en tête"
1364        );
1365        let cqe = ring
1366            .raw_peek_completion_queue_entry()
1367            .expect("la brute est toujours là");
1368        assert_eq!(cqe.user_data, RAW_USER_DATA_TAG);
1369        ring.raw_advance_completion_queue(1);
1370    }
1371
1372    // ── SQ pleine via raw_get → None (back-pressure structurelle). ──
1373    #[test]
1374    #[cfg_attr(miri, ignore = "io_uring non supporté par Miri")]
1375    fn raw_get_returns_none_when_sq_full() {
1376        let mut ring = IoUring::new(nz(8)).expect("ring");
1377        let cap = ring.submission_queue_capacity();
1378        for i in 0..cap {
1379            // SAFETY: NOP tagué ; rempli avant publication.
1380            unsafe {
1381                ring.raw_get_submission_queue_entry()
1382                    .expect("emplacement libre tant que la SQ n'est pas pleine")
1383                    .set_opcode(RawOpcode(IORING_OP_NOP))
1384                    .set_user_data(u64::from(i) | RAW_USER_DATA_TAG);
1385            }
1386        }
1387        // SQ pleine : `prepare` renvoie `None` (aucun syscall, §4.3).
1388        // SAFETY: aucune écriture — on attend `None`.
1389        assert!(
1390            unsafe { ring.raw_get_submission_queue_entry() }.is_none(),
1391            "SQ pleine → None"
1392        );
1393        // Draine proprement (évite de laisser des SQE non publiés au Drop).
1394        ring.submit_and_wait(cap).expect("submit");
1395        ring.raw_advance_completion_queue(cap);
1396        assert!(ring.raw_peek_completion_queue_entry().is_none());
1397    }
1398
1399    // ── Coexistence : niveau 2 (slab) + brut (tagué) sur le MÊME ring. ──
1400    #[test]
1401    #[cfg_attr(miri, ignore = "io_uring non supporté par Miri")]
1402    fn managed_and_raw_coexist_without_collision() {
1403        let mut ring = IoUring::new(nz(16)).expect("ring");
1404        let tok = ring.submit_nop().expect("nop géré");
1405        assert_eq!(
1406            tok.to_user_data() & RAW_USER_DATA_TAG,
1407            0,
1408            "le slab n'utilise jamais le bit 63"
1409        );
1410        let raw_ud = 0x1234 | RAW_USER_DATA_TAG;
1411        // SAFETY: NOP sans buffer ; tagué ; rempli avant publication.
1412        unsafe {
1413            ring.raw_get_submission_queue_entry()
1414                .expect("slot")
1415                .set_opcode(RawOpcode(IORING_OP_NOP))
1416                .set_user_data(raw_ud);
1417        }
1418        ring.submit_and_wait(2).expect("submit 2");
1419
1420        // Drain **insensible à l'ordre** : la brute (tag) part en raw_peek/advance,
1421        // la gérée via try_completion (la moisson gérée saute toute brute en tête).
1422        let (mut got_managed, mut got_raw) = (false, false);
1423        for _ in 0..6 {
1424            if let Some(cqe) = ring.raw_peek_completion_queue_entry()
1425                && cqe.user_data & RAW_USER_DATA_TAG != 0
1426            {
1427                assert_eq!(cqe.user_data, raw_ud, "brute livrée telle quelle");
1428                assert_eq!(cqe.res, 0);
1429                ring.raw_advance_completion_queue(1);
1430                got_raw = true;
1431                continue;
1432            }
1433            if let Some(c) = ring.try_completion() {
1434                assert_eq!(c.token(), tok, "gérée décodée via le slab");
1435                c.completed().expect("nop géré ok");
1436                got_managed = true;
1437            }
1438        }
1439        assert!(
1440            got_managed && got_raw,
1441            "les deux complétions routées sans collision"
1442        );
1443        assert_eq!(ring.in_flight(), 0, "slab cohérent");
1444    }
1445
1446    // ── raw_advance borné au disponible (Principe 4). ──
1447    #[test]
1448    #[cfg_attr(miri, ignore = "io_uring non supporté par Miri")]
1449    fn raw_advance_is_clamped_to_available() {
1450        let mut ring = IoUring::new(nz(8)).expect("ring");
1451        // CQ vide : avance massive = no-op borné (aucune corruption de la tête).
1452        ring.raw_advance_completion_queue(1000);
1453        // SAFETY: NOP tagué, rempli avant publication.
1454        unsafe {
1455            ring.raw_get_submission_queue_entry()
1456                .expect("slot")
1457                .set_opcode(RawOpcode(IORING_OP_NOP))
1458                .set_user_data(RAW_USER_DATA_TAG);
1459        }
1460        ring.submit_and_wait(1).expect("submit");
1461        assert!(ring.raw_peek_completion_queue_entry().is_some());
1462        ring.raw_advance_completion_queue(1000); // borné à 1
1463        assert!(
1464            ring.raw_peek_completion_queue_entry().is_none(),
1465            "consommée, borné"
1466        );
1467    }
1468
1469    #[test]
1470    #[cfg_attr(miri, ignore = "io_uring non supporté par Miri")]
1471    fn capacity_accessors_report_ring_sizes() {
1472        let ring = IoUring::new(nz(8)).expect("ring");
1473        assert!(ring.submission_queue_capacity() >= 8);
1474        assert_eq!(
1475            ring.submission_queue_available(),
1476            ring.submission_queue_capacity()
1477        );
1478        assert!(ring.completion_queue_capacity() >= 8);
1479    }
1480
1481    // ── Sûreté : op brute soumise SANS tag → rejet (debug_assert). ──
1482    #[test]
1483    #[cfg(debug_assertions)]
1484    #[cfg_attr(miri, ignore = "io_uring non supporté par Miri")]
1485    #[should_panic(expected = "RAW_USER_DATA_TAG")]
1486    fn submitting_raw_op_without_tag_panics_in_debug() {
1487        let mut ring = IoUring::new(nz(8)).expect("ring");
1488        // SAFETY: NOP sans buffer ; MAIS `user_data` SANS le tag → viole §5.
1489        unsafe {
1490            ring.raw_get_submission_queue_entry()
1491                .expect("slot")
1492                .set_opcode(RawOpcode(IORING_OP_NOP))
1493                .set_user_data(0x5); // pas de tag !
1494        }
1495        let _ = ring.submit(); // debug_assert dans submit → panique attendue.
1496    }
1497}