Skip to main content

nvme_driver/
block.rs

1use alloc::{boxed::Box, sync::Arc, vec, vec::Vec};
2use core::{
3    any::Any,
4    cell::UnsafeCell,
5    hint::spin_loop,
6    sync::atomic::{AtomicBool, AtomicU16, AtomicU64, AtomicUsize, Ordering},
7};
8
9use dma_api::CoherentArray;
10use log::warn;
11use rdif_block::{
12    BlkError, CompletionSink, DeviceInfo, DriverGeneric, Event, IQueue, IdList, Interface,
13    IrqHandler, IrqSourceInfo, IrqSourceList, QueueInfo, QueueLimits, Request, RequestFlags,
14    RequestId, RequestOp, RequestStatus, validate_request,
15};
16
17use crate::{
18    Namespace, Nvme,
19    err::{Error as NvmeError, Result as NvmeResult},
20    queue::{CommandSet, NvmeCompletion, NvmeQueue as HardwareQueue},
21};
22
23const MAX_PRP_LIST_PAGES: usize = 1;
24const DEFAULT_QUEUE_DEPTH: usize = 64;
25
26struct NvmeBlockInner {
27    nvme: Nvme,
28    namespace: Namespace,
29}
30
31pub struct NvmeBlockDriver {
32    name: &'static str,
33    inner: Arc<NvmeBlockOwner>,
34    queue_depth: usize,
35}
36
37struct NvmeBlockOwner {
38    inner: UnsafeCell<NvmeBlockInner>,
39    queues: UnsafeCell<Vec<Arc<NvmeQueueCore>>>,
40    next_queue_id: AtomicUsize,
41    created_queue_bits: AtomicU64,
42    irq_enabled: AtomicBool,
43    irq_handler_taken: AtomicBool,
44    irq_supported: bool,
45    interrupt_vector: u32,
46}
47
48impl NvmeBlockDriver {
49    pub fn from_nvme(mut nvme: Nvme) -> NvmeResult<Self> {
50        let namespace = nvme
51            .namespace_list()?
52            .into_iter()
53            .next()
54            .ok_or(NvmeError::Unknown("no active namespace found"))?;
55
56        Ok(Self::with_namespace("nvme", nvme, namespace))
57    }
58
59    pub fn from_nvme_with_queue_depth(mut nvme: Nvme, queue_depth: usize) -> NvmeResult<Self> {
60        let namespace = nvme
61            .namespace_list()?
62            .into_iter()
63            .next()
64            .ok_or(NvmeError::Unknown("no active namespace found"))?;
65
66        Ok(Self::with_namespace_and_queue_depth(
67            "nvme",
68            nvme,
69            namespace,
70            queue_depth,
71        ))
72    }
73
74    pub fn with_namespace(name: &'static str, nvme: Nvme, namespace: Namespace) -> Self {
75        Self::with_namespace_and_queue_depth(name, nvme, namespace, DEFAULT_QUEUE_DEPTH)
76    }
77
78    pub fn with_namespace_and_queue_depth(
79        name: &'static str,
80        nvme: Nvme,
81        namespace: Namespace,
82        queue_depth: usize,
83    ) -> Self {
84        let irq_supported = nvme.io_queue_interrupts_enabled();
85        let interrupt_vector = nvme.interrupt_vector();
86        Self {
87            name,
88            inner: Arc::new(NvmeBlockOwner {
89                inner: UnsafeCell::new(NvmeBlockInner { nvme, namespace }),
90                queues: UnsafeCell::new(Vec::new()),
91                next_queue_id: AtomicUsize::new(0),
92                created_queue_bits: AtomicU64::new(0),
93                irq_enabled: AtomicBool::new(false),
94                irq_handler_taken: AtomicBool::new(false),
95                irq_supported,
96                interrupt_vector,
97            }),
98            queue_depth: queue_depth.max(1),
99        }
100    }
101
102    pub fn namespace(&self) -> Namespace {
103        self.inner.with_mut(|inner| inner.namespace)
104    }
105
106    pub fn into_interface(self) -> Self {
107        self
108    }
109
110    fn device_info_for(&self) -> DeviceInfo {
111        self.inner
112            .with_mut(|inner| device_info(self.name, inner.namespace))
113    }
114
115    fn limits_for(&self) -> QueueLimits {
116        self.inner.with_mut(|inner| {
117            limits(
118                inner.nvme.dma_mask(),
119                inner.nvme.page_size(),
120                inner.namespace,
121                self.queue_depth,
122            )
123        })
124    }
125}
126
127// SAFETY: RDIF queue ownership removes task-side sharing of an IO queue. IRQ
128// sharing is mediated by per-queue `NvmeQueueCore` claim guards, and the owner
129// keeps the controller and MMIO mapping alive until all queues are dropped.
130unsafe impl Send for NvmeBlockOwner {}
131
132// SAFETY: Mutable controller access is scoped through `with_mut` during queue
133// creation and namespace queries. The queue registry is populated before the
134// IRQ handler is taken and then read-only for the handler lifetime.
135unsafe impl Sync for NvmeBlockOwner {}
136
137impl NvmeBlockOwner {
138    fn with_mut<R>(&self, f: impl FnOnce(&mut NvmeBlockInner) -> R) -> R {
139        let inner = unsafe { &mut *self.inner.get() };
140        f(inner)
141    }
142
143    fn register_queue(&self, queue: Arc<NvmeQueueCore>) {
144        let queues = unsafe { &mut *self.queues.get() };
145        queues.push(queue);
146    }
147
148    fn queues(&self) -> &[Arc<NvmeQueueCore>] {
149        unsafe { &*self.queues.get() }
150    }
151}
152
153impl DriverGeneric for NvmeBlockDriver {
154    fn name(&self) -> &str {
155        self.name
156    }
157
158    fn raw_any(&self) -> Option<&dyn Any> {
159        Some(self)
160    }
161
162    fn raw_any_mut(&mut self) -> Option<&mut dyn Any> {
163        Some(self)
164    }
165}
166
167impl Interface for NvmeBlockDriver {
168    fn device_info(&self) -> DeviceInfo {
169        self.device_info_for()
170    }
171
172    fn queue_limits(&self) -> QueueLimits {
173        self.limits_for()
174    }
175
176    fn create_queue(&mut self) -> Option<Box<dyn IQueue>> {
177        let id = self.inner.next_queue_id.fetch_add(1, Ordering::Relaxed);
178        if id >= u64::BITS as usize {
179            return None;
180        }
181
182        let queue = self.inner.with_mut(|inner| {
183            let queue = inner.nvme.take_io_queue(id)?;
184            let depth = self.queue_depth.min(queue.depth().saturating_sub(1).max(1));
185            let prp_lists = alloc_prp_lists(&inner.nvme, depth).ok()?;
186            Some(NvmeQueueCore::new(
187                id,
188                depth,
189                self.name,
190                inner.namespace,
191                inner.nvme.dma_mask(),
192                inner.nvme.page_size(),
193                queue,
194                prp_lists,
195            ))
196        })?;
197
198        self.inner.register_queue(queue.clone());
199        self.inner
200            .created_queue_bits
201            .fetch_or(1 << id, Ordering::Release);
202        Some(Box::new(NvmeBlockQueue { core: queue }))
203    }
204
205    fn enable_irq(&self) {
206        if !self.inner.irq_supported {
207            return;
208        }
209        let vector = self.inner.interrupt_vector;
210        self.inner
211            .with_mut(|inner| inner.nvme.unmask_interrupt_vector(vector));
212        self.inner.irq_enabled.store(true, Ordering::Release);
213    }
214
215    fn disable_irq(&self) {
216        if !self.inner.irq_supported {
217            return;
218        }
219        let vector = self.inner.interrupt_vector;
220        self.inner
221            .with_mut(|inner| inner.nvme.mask_interrupt_vector(vector));
222        self.inner.irq_enabled.store(false, Ordering::Release);
223    }
224
225    fn is_irq_enabled(&self) -> bool {
226        self.inner.irq_supported && self.inner.irq_enabled.load(Ordering::Acquire)
227    }
228
229    fn irq_sources(&self) -> IrqSourceList {
230        let queue_bits = self.inner.created_queue_bits.load(Ordering::Acquire);
231        if !self.inner.irq_supported || queue_bits == 0 {
232            return Vec::new();
233        }
234        vec![IrqSourceInfo::legacy(IdList::from_bits(queue_bits))]
235    }
236
237    fn take_irq_handler(&mut self, source_id: usize) -> Option<Box<dyn IrqHandler>> {
238        if source_id != 0 || !self.inner.irq_supported {
239            return None;
240        }
241        if self.inner.created_queue_bits.load(Ordering::Acquire) == 0 {
242            return None;
243        }
244        if self.inner.irq_handler_taken.swap(true, Ordering::AcqRel) {
245            return None;
246        }
247        Some(Box::new(NvmeBlockIrqHandler {
248            owner: self.inner.clone(),
249        }))
250    }
251}
252
253struct NvmeBlockIrqHandler {
254    owner: Arc<NvmeBlockOwner>,
255}
256
257impl IrqHandler for NvmeBlockIrqHandler {
258    fn handle_irq(&self) -> Event {
259        if !self.owner.irq_enabled.load(Ordering::Acquire) {
260            return Event::none();
261        }
262        let mut event = Event::none();
263        for queue in self.owner.queues() {
264            if queue.drain_irq_completions() {
265                event.push_queue(queue.id());
266            }
267        }
268        event
269    }
270}
271
272struct NvmeBlockQueue {
273    core: Arc<NvmeQueueCore>,
274}
275
276struct NvmeQueueCore {
277    id: usize,
278    name: &'static str,
279    namespace: Namespace,
280    dma_mask: u64,
281    page_size: usize,
282    depth: usize,
283    queue: UnsafeCell<HardwareQueue>,
284    state: UnsafeCell<NvmeQueueState>,
285    completion_cache: CompletionCache,
286    state_claimed: AtomicBool,
287    cq_claimed: AtomicBool,
288}
289
290struct NvmeQueueState {
291    slots: Vec<RequestSlot>,
292    free_cids: Vec<usize>,
293    free_prp_lists: Vec<CoherentArray<u64>>,
294}
295
296struct RequestSlot {
297    state: SlotState,
298    prp_list: Option<CoherentArray<u64>>,
299}
300
301#[derive(Clone, Copy, Debug, PartialEq, Eq)]
302enum SlotState {
303    Free,
304    Pending,
305    Complete,
306    Failed,
307}
308
309#[derive(Clone, Copy, Debug, PartialEq, Eq)]
310struct CachedCompletion {
311    cid: usize,
312    status: CompletionStatus,
313}
314
315#[derive(Clone, Copy, Debug, PartialEq, Eq)]
316struct CompletionStatus {
317    success: bool,
318    raw_status: u16,
319    result: u64,
320}
321
322struct CompletionCache {
323    entries: Vec<CompletionCacheEntry>,
324}
325
326struct CompletionCacheEntry {
327    ready: AtomicBool,
328    success: AtomicBool,
329    raw_status: AtomicU16,
330    result: AtomicU64,
331}
332
333struct PrpMapping {
334    prp1: u64,
335    prp2: u64,
336    prp_list: Option<CoherentArray<u64>>,
337}
338
339impl NvmeQueueCore {
340    #[allow(clippy::too_many_arguments)]
341    fn new(
342        id: usize,
343        depth: usize,
344        name: &'static str,
345        namespace: Namespace,
346        dma_mask: u64,
347        page_size: usize,
348        queue: HardwareQueue,
349        prp_lists: Vec<CoherentArray<u64>>,
350    ) -> Arc<Self> {
351        let mut slots = Vec::with_capacity(depth + 1);
352        slots.resize_with(depth + 1, || RequestSlot {
353            state: SlotState::Free,
354            prp_list: None,
355        });
356        let free_cids = (1..=depth).rev().collect();
357
358        Arc::new(Self {
359            id,
360            name,
361            namespace,
362            dma_mask,
363            page_size,
364            depth,
365            queue: UnsafeCell::new(queue),
366            state: UnsafeCell::new(NvmeQueueState {
367                slots,
368                free_cids,
369                free_prp_lists: prp_lists,
370            }),
371            completion_cache: CompletionCache::new(depth + 1),
372            state_claimed: AtomicBool::new(false),
373            cq_claimed: AtomicBool::new(false),
374        })
375    }
376
377    const fn id(&self) -> usize {
378        self.id
379    }
380
381    fn queue_info(&self) -> QueueInfo {
382        QueueInfo {
383            id: self.id,
384            device: device_info(self.name, self.namespace),
385            limits: limits(self.dma_mask, self.page_size, self.namespace, self.depth),
386        }
387    }
388
389    fn with_claim<R>(&self, f: impl FnOnce(&mut NvmeQueueState) -> R) -> R {
390        while self
391            .state_claimed
392            .compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed)
393            .is_err()
394        {
395            spin_loop();
396        }
397        let state = unsafe { &mut *self.state.get() };
398        let result = f(state);
399        self.state_claimed.store(false, Ordering::Release);
400        result
401    }
402
403    fn with_cq_claim<R>(&self, f: impl FnOnce(&HardwareQueue) -> R) -> R {
404        while self
405            .cq_claimed
406            .compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed)
407            .is_err()
408        {
409            spin_loop();
410        }
411        let queue = unsafe { &*self.queue.get() };
412        let result = f(queue);
413        self.cq_claimed.store(false, Ordering::Release);
414        result
415    }
416
417    fn try_with_cq_claim<R>(&self, f: impl FnOnce(&HardwareQueue) -> R) -> Option<R> {
418        if self
419            .cq_claimed
420            .compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed)
421            .is_err()
422        {
423            return None;
424        }
425        let queue = unsafe { &*self.queue.get() };
426        let result = f(queue);
427        self.cq_claimed.store(false, Ordering::Release);
428        Some(result)
429    }
430
431    fn drain_irq_completions(&self) -> bool {
432        self.try_with_cq_claim(drain_hardware_completions_to_vec)
433            .map(|completions| self.cache_completions(completions))
434            .unwrap_or(true)
435    }
436
437    fn drain_completions(&self) -> bool {
438        let completions = self.with_cq_claim(drain_hardware_completions_to_vec);
439        self.cache_completions(completions)
440    }
441
442    fn cache_completions(&self, completions: Vec<CachedCompletion>) -> bool {
443        if completions.is_empty() {
444            return false;
445        }
446        self.completion_cache.extend(completions);
447        true
448    }
449}
450
451// SAFETY: Slot, CID, and completion-cache access is serialized through
452// `state_claimed`; hardware CQ access is serialized through `cq_claimed`. SQ
453// submission is only driven by the single RDIF queue owner. The MMIO mapping
454// and DMA buffers outlive this core through the owner/interface lifetime.
455unsafe impl Send for NvmeQueueCore {}
456
457// SAFETY: Shared references are used by task context and hard IRQ context, but
458// shared mutable state is guarded as described in the `Send` impl.
459unsafe impl Sync for NvmeQueueCore {}
460
461impl NvmeQueueState {
462    fn alloc_cid(&mut self) -> Result<usize, BlkError> {
463        self.free_cids.pop().ok_or(BlkError::Retry)
464    }
465
466    fn free_cid(&mut self, cid: usize) {
467        if cid < self.slots.len() {
468            if let Some(prp_list) = self.slots[cid].prp_list.take() {
469                self.free_prp_lists.push(prp_list);
470            }
471            self.slots[cid].state = SlotState::Free;
472            self.free_cids.push(cid);
473        }
474    }
475
476    fn build_command(
477        &mut self,
478        namespace: Namespace,
479        page_size: usize,
480        cid: usize,
481        request: &Request<'_>,
482    ) -> Result<CommandSet, BlkError> {
483        let cid = u16::try_from(cid).map_err(|_| BlkError::InvalidRequest)?;
484        match request.op {
485            RequestOp::Read | RequestOp::Write => {
486                let prp = self.build_prp_mapping(page_size, request)?;
487                let command = match request.op {
488                    RequestOp::Read => CommandSet::nvm_cmd_read_with_cid(
489                        namespace.id,
490                        prp.prp1,
491                        prp.prp2,
492                        request.lba,
493                        request.block_count,
494                        cid,
495                    ),
496                    RequestOp::Write => CommandSet::nvm_cmd_write_with_cid(
497                        namespace.id,
498                        prp.prp1,
499                        prp.prp2,
500                        request.lba,
501                        request.block_count,
502                        cid,
503                    ),
504                    _ => unreachable!(),
505                };
506                self.slots[usize::from(cid)].prp_list = prp.prp_list;
507                Ok(command)
508            }
509            RequestOp::Flush => Ok(CommandSet::nvm_cmd_flush_with_cid(namespace.id, cid)),
510            RequestOp::Discard | RequestOp::WriteZeroes => Err(BlkError::NotSupported),
511        }
512    }
513
514    fn build_prp_mapping(
515        &mut self,
516        page_size: usize,
517        request: &Request<'_>,
518    ) -> Result<PrpMapping, BlkError> {
519        let mut prps = PrpPageAccumulator::new();
520        for segment in request.segments.iter() {
521            prps.push_segment(segment.bus, segment.len, page_size)?;
522        }
523        let pages = prps.into_pages();
524        let prp1 = *pages.first().ok_or(BlkError::InvalidRequest)?;
525        let prp2 = match pages.len() {
526            1 => 0,
527            2 => pages[1],
528            _ => {
529                let list_entries = page_size / core::mem::size_of::<u64>();
530                if pages.len() - 1 > list_entries * MAX_PRP_LIST_PAGES {
531                    return Err(BlkError::InvalidRequest);
532                }
533                let mut list = self.free_prp_lists.pop().ok_or(BlkError::Retry)?;
534                for entry in 0..list_entries {
535                    list.set_cpu(entry, 0);
536                }
537                for (entry, addr) in pages[1..].iter().copied().enumerate() {
538                    list.set_cpu(entry, addr);
539                }
540                let addr = list.dma_addr().as_u64();
541                return Ok(PrpMapping {
542                    prp1,
543                    prp2: addr,
544                    prp_list: Some(list),
545                });
546            }
547        };
548        Ok(PrpMapping {
549            prp1,
550            prp2,
551            prp_list: None,
552        })
553    }
554
555    fn consume_cached_completions(&mut self, queue_id: usize, cache: &CompletionCache) -> usize {
556        cache.drain_into_slots(queue_id, &mut self.slots)
557    }
558}
559
560fn drain_hardware_completions_to_vec(queue: &HardwareQueue) -> Vec<CachedCompletion> {
561    let mut completions = Vec::new();
562    while let Some(completion) = queue.poll_completion() {
563        completions.push(CachedCompletion::from(completion));
564    }
565    completions
566}
567
568impl CompletionCache {
569    fn new(capacity: usize) -> Self {
570        let mut entries = Vec::with_capacity(capacity);
571        entries.resize_with(capacity, CompletionCacheEntry::new);
572        Self { entries }
573    }
574
575    fn extend(&self, completions: Vec<CachedCompletion>) {
576        for completion in completions {
577            self.record(completion);
578        }
579    }
580
581    fn record(&self, completion: CachedCompletion) {
582        let Some(entry) = self.entries.get(completion.cid) else {
583            return;
584        };
585        entry
586            .success
587            .store(completion.status.success, Ordering::Relaxed);
588        entry
589            .raw_status
590            .store(completion.status.raw_status, Ordering::Relaxed);
591        entry
592            .result
593            .store(completion.status.result, Ordering::Relaxed);
594        entry.ready.store(true, Ordering::Release);
595    }
596
597    fn drain_into_slots(&self, queue_id: usize, slots: &mut [RequestSlot]) -> usize {
598        let mut consumed = 0;
599        for (cid, entry) in self.entries.iter().enumerate() {
600            if !entry.ready.swap(false, Ordering::AcqRel) {
601                continue;
602            }
603            let Some(slot) = slots.get_mut(cid) else {
604                continue;
605            };
606            let status = CompletionStatus {
607                success: entry.success.load(Ordering::Relaxed),
608                raw_status: entry.raw_status.load(Ordering::Relaxed),
609                result: entry.result.load(Ordering::Relaxed),
610            };
611            slot.state = if status.success {
612                SlotState::Complete
613            } else {
614                warn!(
615                    "nvme queue {} request {} failed: status={:#x}, result={:#x}",
616                    queue_id, cid, status.raw_status, status.result
617                );
618                SlotState::Failed
619            };
620            consumed += 1;
621        }
622        consumed
623    }
624}
625
626impl CompletionCacheEntry {
627    fn new() -> Self {
628        Self {
629            ready: AtomicBool::new(false),
630            success: AtomicBool::new(false),
631            raw_status: AtomicU16::new(0),
632            result: AtomicU64::new(0),
633        }
634    }
635}
636
637impl From<NvmeCompletion> for CachedCompletion {
638    fn from(completion: NvmeCompletion) -> Self {
639        Self {
640            cid: usize::from(completion.command_id),
641            status: CompletionStatus {
642                success: completion.status.is_success(),
643                raw_status: completion.status.0,
644                result: completion.result,
645            },
646        }
647    }
648}
649
650// SAFETY: NVMe queues may access submitted request DMA segments until the
651// matching completion is reclaimed by `poll_request`. Slots are freed only
652// after completion/error, and no segment pointers are accessed after that.
653unsafe impl IQueue for NvmeBlockQueue {
654    fn id(&self) -> usize {
655        self.core.id()
656    }
657
658    fn info(&self) -> QueueInfo {
659        self.core.queue_info()
660    }
661
662    fn submit_request(&mut self, request: Request<'_>) -> Result<RequestId, BlkError> {
663        let info = self.core.queue_info();
664        validate_request(info, &request)?;
665        let namespace = self.core.namespace;
666        let page_size = self.core.page_size;
667        let queue_id = self.core.id();
668
669        self.core.drain_completions();
670        self.core.with_claim(|state| {
671            state.consume_cached_completions(queue_id, &self.core.completion_cache);
672
673            let cid = state.alloc_cid()?;
674            let command = match state.build_command(namespace, page_size, cid, &request) {
675                Ok(command) => command,
676                Err(err) => {
677                    state.free_cid(cid);
678                    return Err(err);
679                }
680            };
681            state.slots[cid].state = SlotState::Pending;
682            let queue = unsafe { &*self.core.queue.get() };
683            queue.submit_io_data(command);
684            Ok(RequestId::new(cid))
685        })
686    }
687
688    fn poll_request(&mut self, request: RequestId) -> Result<RequestStatus, BlkError> {
689        let queue_id = self.core.id();
690        self.core.drain_completions();
691        self.core.with_claim(|state| {
692            state.consume_cached_completions(queue_id, &self.core.completion_cache);
693
694            let cid = usize::from(request);
695            match state.slots.get(cid).map(|slot| slot.state) {
696                Some(SlotState::Pending) => Ok(RequestStatus::Pending),
697                Some(SlotState::Complete) => {
698                    state.free_cid(cid);
699                    Ok(RequestStatus::Complete)
700                }
701                Some(SlotState::Failed) => {
702                    state.free_cid(cid);
703                    Err(BlkError::Io)
704                }
705                Some(SlotState::Free) | None => Err(BlkError::InvalidRequest),
706            }
707        })
708    }
709
710    fn poll_completions(
711        &mut self,
712        requests: &[RequestId],
713        sink: &mut dyn CompletionSink,
714    ) -> Result<(), BlkError> {
715        let queue_id = self.core.id();
716        self.core.drain_completions();
717        self.core.with_claim(|state| {
718            state.consume_cached_completions(queue_id, &self.core.completion_cache);
719
720            for &request in requests {
721                let cid = usize::from(request);
722                match state.slots.get(cid).map(|slot| slot.state) {
723                    Some(SlotState::Pending) => {}
724                    Some(SlotState::Complete) => {
725                        state.free_cid(cid);
726                        sink.complete(request, Ok(()));
727                    }
728                    Some(SlotState::Failed) => {
729                        state.free_cid(cid);
730                        sink.complete(request, Err(BlkError::Io));
731                    }
732                    Some(SlotState::Free) | None => {
733                        sink.complete(request, Err(BlkError::InvalidRequest))
734                    }
735                }
736            }
737            Ok(())
738        })
739    }
740}
741
742fn alloc_prp_lists(nvme: &Nvme, depth: usize) -> NvmeResult<Vec<CoherentArray<u64>>> {
743    let mut lists = Vec::with_capacity(depth);
744    for _ in 0..depth {
745        lists.push(nvme.alloc_prp_list()?);
746    }
747    Ok(lists)
748}
749
750#[derive(Default)]
751struct PrpPageAccumulator {
752    pages: Vec<u64>,
753    last_end: Option<u64>,
754    current_page_end: Option<u64>,
755}
756
757impl PrpPageAccumulator {
758    const fn new() -> Self {
759        Self {
760            pages: Vec::new(),
761            last_end: None,
762            current_page_end: None,
763        }
764    }
765
766    fn into_pages(self) -> Vec<u64> {
767        self.pages
768    }
769
770    fn push_segment(&mut self, addr: u64, len: usize, page_size: usize) -> Result<(), BlkError> {
771        if page_size == 0 || len == 0 {
772            return Err(BlkError::InvalidRequest);
773        }
774        let page_size = u64::try_from(page_size).map_err(|_| BlkError::InvalidRequest)?;
775        let end = addr
776            .checked_add(u64::try_from(len).map_err(|_| BlkError::InvalidRequest)?)
777            .ok_or(BlkError::InvalidRequest)?;
778        let mut cursor = addr;
779
780        while cursor < end {
781            self.ensure_page_entry(cursor, page_size)?;
782            let page_end = self.current_page_end.ok_or(BlkError::InvalidRequest)?;
783            let chunk_end = page_end.min(end);
784            if chunk_end <= cursor {
785                return Err(BlkError::InvalidRequest);
786            }
787            cursor = chunk_end;
788            self.last_end = Some(cursor);
789        }
790
791        Ok(())
792    }
793
794    fn ensure_page_entry(&mut self, cursor: u64, page_size: u64) -> Result<(), BlkError> {
795        let Some(last_end) = self.last_end else {
796            self.push_page(cursor, page_size)?;
797            return Ok(());
798        };
799        let current_page_end = self.current_page_end.ok_or(BlkError::InvalidRequest)?;
800
801        if cursor < last_end {
802            return Err(BlkError::InvalidRequest);
803        }
804        if cursor == last_end && cursor < current_page_end {
805            return Ok(());
806        }
807        if cursor != last_end && last_end != current_page_end {
808            return Err(BlkError::InvalidRequest);
809        }
810        if !cursor.is_multiple_of(page_size) {
811            return Err(BlkError::InvalidRequest);
812        }
813        self.push_page(cursor, page_size)
814    }
815
816    fn push_page(&mut self, addr: u64, page_size: u64) -> Result<(), BlkError> {
817        let page_base = addr / page_size * page_size;
818        let page_end = page_base
819            .checked_add(page_size)
820            .ok_or(BlkError::InvalidRequest)?;
821        self.pages.push(addr);
822        self.current_page_end = Some(page_end);
823        Ok(())
824    }
825}
826
827fn device_info(name: &'static str, namespace: Namespace) -> DeviceInfo {
828    DeviceInfo {
829        name: Some(name),
830        model: Some("nvme"),
831        ..DeviceInfo::new(namespace.lba_count as u64, namespace.lba_size)
832    }
833}
834
835fn limits(
836    dma_mask: u64,
837    page_size: usize,
838    namespace: Namespace,
839    max_inflight: usize,
840) -> QueueLimits {
841    let dma_alignment = page_size.max(namespace.lba_size.max(1));
842    let prp_entries = page_size / core::mem::size_of::<u64>();
843    let max_bytes = page_size.saturating_mul(prp_entries + 1);
844    let max_blocks = max_bytes
845        .checked_div(namespace.lba_size.max(1))
846        .unwrap_or(1)
847        .max(1)
848        .min(u16::MAX as usize + 1) as u32;
849    QueueLimits {
850        dma_mask,
851        dma_alignment,
852        max_inflight: max_inflight.max(1),
853        max_blocks_per_request: max_blocks,
854        max_segments: prp_entries + 1,
855        max_segment_size: max_bytes,
856        supported_flags: RequestFlags::NONE,
857        supports_flush: true,
858        supports_discard: false,
859        supports_write_zeroes: false,
860    }
861}
862
863#[cfg(test)]
864mod tests {
865    use super::{
866        CachedCompletion, CompletionCache, CompletionStatus, PrpPageAccumulator, RequestSlot,
867        SlotState, limits,
868    };
869    use crate::Namespace;
870
871    #[test]
872    fn queue_limits_align_dma_to_nvme_page_size() {
873        let namespace = Namespace {
874            id: 1,
875            lba_size: 512,
876            lba_count: 1024,
877            metadata_size: 0,
878        };
879        let limits = limits(u64::MAX, 4096, namespace, 8);
880
881        assert_eq!(limits.dma_alignment, 4096);
882        assert_eq!(limits.max_segments, 513);
883        assert_eq!(limits.max_segment_size, 4096 * 513);
884        assert!(limits.max_blocks_per_request >= 8);
885    }
886
887    #[test]
888    fn queue_limits_keep_prp_capacity_tied_to_controller_page() {
889        let namespace = Namespace {
890            id: 1,
891            lba_size: 8192,
892            lba_count: 1024,
893            metadata_size: 0,
894        };
895        let limits = limits(u64::MAX, 4096, namespace, 8);
896
897        assert_eq!(limits.dma_alignment, 8192);
898        assert_eq!(limits.max_segments, 513);
899        assert_eq!(limits.max_segment_size, 4096 * 513);
900        assert_eq!(limits.max_blocks_per_request, 256);
901    }
902
903    #[test]
904    fn prp_pages_split_at_controller_page_boundaries() {
905        let mut pages = PrpPageAccumulator::new();
906
907        pages.push_segment(0x1800, 4096, 4096).unwrap();
908
909        assert_eq!(pages.into_pages(), [0x1800, 0x2000]);
910    }
911
912    #[test]
913    fn prp_pages_coalesce_contiguous_split_segments() {
914        let mut pages = PrpPageAccumulator::new();
915
916        pages.push_segment(0x1000, 4096, 4096).unwrap();
917        pages.push_segment(0x2000, 2048, 4096).unwrap();
918        pages.push_segment(0x2800, 2048, 4096).unwrap();
919
920        assert_eq!(pages.into_pages(), [0x1000, 0x2000]);
921    }
922
923    #[test]
924    fn prp_pages_reject_unaligned_non_contiguous_segment() {
925        let mut pages = PrpPageAccumulator::new();
926
927        pages.push_segment(0x1000, 2048, 4096).unwrap();
928
929        assert!(pages.push_segment(0x2800, 512, 4096).is_err());
930    }
931
932    #[test]
933    fn cached_completion_does_not_complete_slot_until_task_consumes_it() {
934        let cache = CompletionCache::new(4);
935        let mut slots = test_slots(4);
936        slots[2].state = SlotState::Pending;
937
938        cache.extend(alloc::vec![CachedCompletion::success(2)]);
939
940        assert_eq!(slots[2].state, SlotState::Pending);
941        assert_eq!(cache.drain_into_slots(0, &mut slots), 1);
942        assert_eq!(slots[2].state, SlotState::Complete);
943    }
944
945    #[test]
946    fn cached_failed_completion_marks_slot_failed_in_task_context() {
947        let cache = CompletionCache::new(4);
948        let mut slots = test_slots(4);
949        slots[3].state = SlotState::Pending;
950
951        cache.extend(alloc::vec![CachedCompletion::failed(3, 0x4002)]);
952
953        assert_eq!(cache.drain_into_slots(0, &mut slots), 1);
954        assert_eq!(slots[3].state, SlotState::Failed);
955    }
956
957    #[test]
958    fn cached_completion_is_consumed_once() {
959        let cache = CompletionCache::new(2);
960        let mut slots = test_slots(2);
961        slots[1].state = SlotState::Pending;
962
963        cache.extend(alloc::vec![CachedCompletion::success(1)]);
964
965        assert_eq!(cache.drain_into_slots(0, &mut slots), 1);
966        assert_eq!(cache.drain_into_slots(0, &mut slots), 0);
967        assert_eq!(slots[1].state, SlotState::Complete);
968    }
969
970    fn test_slots(count: usize) -> alloc::vec::Vec<RequestSlot> {
971        (0..count)
972            .map(|_| RequestSlot {
973                state: SlotState::Free,
974                prp_list: None,
975            })
976            .collect()
977    }
978
979    impl CachedCompletion {
980        const fn success(cid: usize) -> Self {
981            Self {
982                cid,
983                status: CompletionStatus {
984                    success: true,
985                    raw_status: 0,
986                    result: 0,
987                },
988            }
989        }
990
991        const fn failed(cid: usize, raw_status: u16) -> Self {
992            Self {
993                cid,
994                status: CompletionStatus {
995                    success: false,
996                    raw_status,
997                    result: 0,
998                },
999            }
1000        }
1001    }
1002}