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