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
127unsafe impl Send for NvmeBlockOwner {}
131
132unsafe 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
451unsafe impl Send for NvmeQueueCore {}
456
457unsafe 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
650unsafe 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}