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
128unsafe impl Send for NvmeBlockOwner {}
132
133unsafe 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
462unsafe impl Send for NvmeQueueCore {}
467
468unsafe 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
661unsafe 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 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}