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    sync::atomic::{AtomicBool, AtomicU64, AtomicUsize, Ordering},
6};
7
8use dma_api::CoherentArray;
9use rdif_block::{
10    BlkError, DeviceInfo, DriverGeneric, Event, IQueue, IdList, Interface, IrqHandler,
11    IrqSourceInfo, IrqSourceList, QueueInfo, QueueLimits, Request, RequestFlags, RequestId,
12    RequestOp, RequestStatus, validate_request,
13};
14
15use crate::{
16    Namespace, Nvme,
17    err::{Error as NvmeError, Result as NvmeResult},
18    queue::{CommandSet, NvmeQueue as HardwareQueue},
19};
20
21const MAX_PRP_LIST_PAGES: usize = 1;
22const DEFAULT_QUEUE_DEPTH: usize = 64;
23
24struct NvmeBlockInner {
25    nvme: Nvme,
26    namespace: Namespace,
27}
28
29pub struct NvmeBlockDriver {
30    name: &'static str,
31    inner: Arc<NvmeBlockOwner>,
32    queue_depth: usize,
33    irq_handler_taken: bool,
34}
35
36struct NvmeBlockOwner {
37    inner: UnsafeCell<NvmeBlockInner>,
38    next_queue_id: AtomicUsize,
39    irq_enabled: AtomicBool,
40    pending_irq: AtomicU64,
41    created_queues: AtomicU64,
42}
43
44impl NvmeBlockDriver {
45    pub fn from_nvme(mut nvme: Nvme) -> NvmeResult<Self> {
46        let namespace = nvme
47            .namespace_list()?
48            .into_iter()
49            .next()
50            .ok_or(NvmeError::Unknown("no active namespace found"))?;
51
52        Ok(Self::with_namespace("nvme", nvme, namespace))
53    }
54
55    pub fn from_nvme_with_queue_depth(mut nvme: Nvme, queue_depth: usize) -> NvmeResult<Self> {
56        let namespace = nvme
57            .namespace_list()?
58            .into_iter()
59            .next()
60            .ok_or(NvmeError::Unknown("no active namespace found"))?;
61
62        Ok(Self::with_namespace_and_queue_depth(
63            "nvme",
64            nvme,
65            namespace,
66            queue_depth,
67        ))
68    }
69
70    pub fn with_namespace(name: &'static str, nvme: Nvme, namespace: Namespace) -> Self {
71        Self::with_namespace_and_queue_depth(name, nvme, namespace, DEFAULT_QUEUE_DEPTH)
72    }
73
74    pub fn with_namespace_and_queue_depth(
75        name: &'static str,
76        nvme: Nvme,
77        namespace: Namespace,
78        queue_depth: usize,
79    ) -> Self {
80        Self {
81            name,
82            inner: Arc::new(NvmeBlockOwner {
83                inner: UnsafeCell::new(NvmeBlockInner { nvme, namespace }),
84                next_queue_id: AtomicUsize::new(0),
85                irq_enabled: AtomicBool::new(true),
86                pending_irq: AtomicU64::new(0),
87                created_queues: AtomicU64::new(0),
88            }),
89            queue_depth: queue_depth.max(1),
90            irq_handler_taken: false,
91        }
92    }
93
94    pub fn namespace(&self) -> Namespace {
95        self.inner.with_mut(|inner| inner.namespace)
96    }
97
98    pub fn into_interface(self) -> Self {
99        self
100    }
101
102    fn device_info_for(&self) -> DeviceInfo {
103        self.inner
104            .with_mut(|inner| device_info(self.name, inner.namespace))
105    }
106
107    fn limits_for(&self) -> QueueLimits {
108        self.inner.with_mut(|inner| {
109            limits(
110                inner.nvme.dma_mask(),
111                inner.nvme.page_size(),
112                inner.namespace,
113            )
114        })
115    }
116}
117
118// SAFETY: RDIF queue ownership removes task-side sharing of an IO queue. The
119// exported IRQ handler only touches atomics and never borrows the controller.
120// The owner keeps the controller and MMIO mapping alive until all queues and
121// handlers are dropped.
122unsafe impl Send for NvmeBlockOwner {}
123
124// SAFETY: Mutable controller access is scoped through `with_mut` during queue
125// creation and namespace queries. Runtime IRQ callbacks use only atomics.
126unsafe impl Sync for NvmeBlockOwner {}
127
128impl NvmeBlockOwner {
129    fn with_mut<R>(&self, f: impl FnOnce(&mut NvmeBlockInner) -> R) -> R {
130        let inner = unsafe { &mut *self.inner.get() };
131        f(inner)
132    }
133}
134
135impl DriverGeneric for NvmeBlockDriver {
136    fn name(&self) -> &str {
137        self.name
138    }
139
140    fn raw_any(&self) -> Option<&dyn Any> {
141        Some(self)
142    }
143
144    fn raw_any_mut(&mut self) -> Option<&mut dyn Any> {
145        Some(self)
146    }
147}
148
149impl Interface for NvmeBlockDriver {
150    fn device_info(&self) -> DeviceInfo {
151        self.device_info_for()
152    }
153
154    fn queue_limits(&self) -> QueueLimits {
155        self.limits_for()
156    }
157
158    fn create_queue(&mut self) -> Option<Box<dyn IQueue>> {
159        let id = self.inner.next_queue_id.fetch_add(1, Ordering::Relaxed);
160        if id >= u64::BITS as usize {
161            return None;
162        }
163
164        let queue = self.inner.with_mut(|inner| {
165            let queue = inner.nvme.take_io_queue(id)?;
166            let depth = self.queue_depth.min(queue.depth().saturating_sub(1).max(1));
167            let prp_lists = alloc_prp_lists(&inner.nvme, depth).ok()?;
168            Some(NvmeBlockQueue::new(
169                id,
170                depth,
171                self.name,
172                inner.namespace,
173                inner.nvme.dma_mask(),
174                inner.nvme.page_size(),
175                queue,
176                prp_lists,
177                Arc::clone(&self.inner),
178            ))
179        })?;
180
181        self.inner
182            .created_queues
183            .fetch_or(1 << id, Ordering::AcqRel);
184        Some(Box::new(queue))
185    }
186
187    fn enable_irq(&self) {
188        self.inner.irq_enabled.store(true, Ordering::Release);
189    }
190
191    fn disable_irq(&self) {
192        self.inner.irq_enabled.store(false, Ordering::Release);
193    }
194
195    fn is_irq_enabled(&self) -> bool {
196        self.inner.irq_enabled.load(Ordering::Acquire)
197    }
198
199    fn irq_sources(&self) -> IrqSourceList {
200        let queues = IdList::from_bits(self.inner.created_queues.load(Ordering::Acquire));
201        vec![IrqSourceInfo::legacy(queues)]
202    }
203
204    fn take_irq_handler(&mut self, source_id: usize) -> Option<Box<dyn IrqHandler>> {
205        if source_id != 0 || self.irq_handler_taken {
206            return None;
207        }
208        self.irq_handler_taken = true;
209        Some(Box::new(NvmeIrqHandler {
210            inner: Arc::clone(&self.inner),
211        }))
212    }
213}
214
215struct NvmeIrqHandler {
216    inner: Arc<NvmeBlockOwner>,
217}
218
219impl IrqHandler for NvmeIrqHandler {
220    fn handle_irq(&self) -> Event {
221        if !self.inner.irq_enabled.load(Ordering::Acquire) {
222            return Event::none();
223        }
224        let pending = self.inner.pending_irq.swap(0, Ordering::AcqRel);
225        if pending == 0 {
226            Event::from_queue_bits(self.inner.created_queues.load(Ordering::Acquire))
227        } else {
228            Event::from_queue_bits(pending)
229        }
230    }
231}
232
233struct NvmeBlockQueue {
234    id: usize,
235    name: &'static str,
236    namespace: Namespace,
237    dma_mask: u64,
238    page_size: usize,
239    queue: HardwareQueue,
240    slots: Vec<RequestSlot>,
241    free_cids: Vec<usize>,
242    free_prp_lists: Vec<CoherentArray<u64>>,
243    owner: Arc<NvmeBlockOwner>,
244}
245
246struct RequestSlot {
247    state: SlotState,
248    prp_list: Option<CoherentArray<u64>>,
249}
250
251#[derive(Clone, Copy, Debug, PartialEq, Eq)]
252enum SlotState {
253    Free,
254    Pending,
255    Complete,
256    Failed,
257}
258
259struct PrpMapping {
260    prp1: u64,
261    prp2: u64,
262    prp_list: Option<CoherentArray<u64>>,
263}
264
265impl NvmeBlockQueue {
266    #[allow(clippy::too_many_arguments)]
267    fn new(
268        id: usize,
269        depth: usize,
270        name: &'static str,
271        namespace: Namespace,
272        dma_mask: u64,
273        page_size: usize,
274        queue: HardwareQueue,
275        prp_lists: Vec<CoherentArray<u64>>,
276        owner: Arc<NvmeBlockOwner>,
277    ) -> Self {
278        let mut slots = Vec::with_capacity(depth + 1);
279        slots.resize_with(depth + 1, || RequestSlot {
280            state: SlotState::Free,
281            prp_list: None,
282        });
283        let free_cids = (1..=depth).rev().collect();
284
285        Self {
286            id,
287            name,
288            namespace,
289            dma_mask,
290            page_size,
291            queue,
292            slots,
293            free_cids,
294            free_prp_lists: prp_lists,
295            owner,
296        }
297    }
298
299    fn queue_info(&self) -> QueueInfo {
300        QueueInfo {
301            id: self.id,
302            device: device_info(self.name, self.namespace),
303            limits: limits(self.dma_mask, self.page_size, self.namespace),
304        }
305    }
306
307    fn alloc_cid(&mut self) -> Result<usize, BlkError> {
308        self.free_cids.pop().ok_or(BlkError::Retry)
309    }
310
311    fn free_cid(&mut self, cid: usize) {
312        if cid < self.slots.len() {
313            if let Some(prp_list) = self.slots[cid].prp_list.take() {
314                self.free_prp_lists.push(prp_list);
315            }
316            self.slots[cid].state = SlotState::Free;
317            self.free_cids.push(cid);
318        }
319    }
320
321    fn build_command(&mut self, cid: usize, request: &Request<'_>) -> Result<CommandSet, BlkError> {
322        let cid = u16::try_from(cid).map_err(|_| BlkError::InvalidRequest)?;
323        match request.op {
324            RequestOp::Read | RequestOp::Write => {
325                let prp = self.build_prp_mapping(request)?;
326                let command = match request.op {
327                    RequestOp::Read => CommandSet::nvm_cmd_read_with_cid(
328                        self.namespace.id,
329                        prp.prp1,
330                        prp.prp2,
331                        request.lba,
332                        request.block_count,
333                        cid,
334                    ),
335                    RequestOp::Write => CommandSet::nvm_cmd_write_with_cid(
336                        self.namespace.id,
337                        prp.prp1,
338                        prp.prp2,
339                        request.lba,
340                        request.block_count,
341                        cid,
342                    ),
343                    _ => unreachable!(),
344                };
345                self.slots[usize::from(cid)].prp_list = prp.prp_list;
346                Ok(command)
347            }
348            RequestOp::Flush => Ok(CommandSet::nvm_cmd_flush_with_cid(self.namespace.id, cid)),
349            RequestOp::Discard | RequestOp::WriteZeroes => Err(BlkError::NotSupported),
350        }
351    }
352
353    fn build_prp_mapping(&mut self, request: &Request<'_>) -> Result<PrpMapping, BlkError> {
354        let mut pages = Vec::new();
355        for segment in request.segments.iter() {
356            push_prp_pages(&mut pages, segment.bus, segment.len, self.page_size)?;
357        }
358        let prp1 = *pages.first().ok_or(BlkError::InvalidRequest)?;
359        let prp2 = match pages.len() {
360            1 => 0,
361            2 => pages[1],
362            _ => {
363                let list_entries = self.page_size / core::mem::size_of::<u64>();
364                if pages.len() - 1 > list_entries * MAX_PRP_LIST_PAGES {
365                    return Err(BlkError::InvalidRequest);
366                }
367                let mut list = self.free_prp_lists.pop().ok_or(BlkError::Retry)?;
368                for entry in 0..list_entries {
369                    list.set_cpu(entry, 0);
370                }
371                for (entry, addr) in pages[1..].iter().copied().enumerate() {
372                    list.set_cpu(entry, addr);
373                }
374                let addr = list.dma_addr().as_u64();
375                return Ok(PrpMapping {
376                    prp1,
377                    prp2: addr,
378                    prp_list: Some(list),
379                });
380            }
381        };
382        Ok(PrpMapping {
383            prp1,
384            prp2,
385            prp_list: None,
386        })
387    }
388
389    fn drain_completions(&mut self) {
390        while let Some(completion) = self.queue.poll_completion() {
391            let cid = usize::from(completion.command_id);
392            if let Some(slot) = self.slots.get_mut(cid) {
393                slot.state = if completion.status.is_success() {
394                    SlotState::Complete
395                } else {
396                    SlotState::Failed
397                };
398            }
399        }
400    }
401
402    fn insert_pending_irq(&self) {
403        if self.owner.irq_enabled.load(Ordering::Acquire) && self.id < u64::BITS as usize {
404            self.owner
405                .pending_irq
406                .fetch_or(1 << self.id, Ordering::AcqRel);
407        }
408    }
409}
410
411// SAFETY: NVMe queues may access submitted request DMA segments until the
412// matching completion is reclaimed by `poll_request`. Slots are freed only
413// after completion/error, and no segment pointers are accessed after that.
414unsafe impl IQueue for NvmeBlockQueue {
415    fn id(&self) -> usize {
416        self.id
417    }
418
419    fn info(&self) -> QueueInfo {
420        self.queue_info()
421    }
422
423    fn submit_request(&mut self, request: Request<'_>) -> Result<RequestId, BlkError> {
424        let info = self.queue_info();
425        validate_request(info, &request)?;
426        self.drain_completions();
427
428        let cid = self.alloc_cid()?;
429        let command = match self.build_command(cid, &request) {
430            Ok(command) => command,
431            Err(err) => {
432                self.free_cid(cid);
433                return Err(err);
434            }
435        };
436        self.slots[cid].state = SlotState::Pending;
437        self.queue.submit_io_data(command);
438        self.insert_pending_irq();
439        Ok(RequestId::new(cid))
440    }
441
442    fn poll_request(&mut self, request: RequestId) -> Result<RequestStatus, BlkError> {
443        self.drain_completions();
444
445        let cid = usize::from(request);
446        match self.slots.get(cid).map(|slot| slot.state) {
447            Some(SlotState::Pending) => Ok(RequestStatus::Pending),
448            Some(SlotState::Complete) => {
449                self.free_cid(cid);
450                Ok(RequestStatus::Complete)
451            }
452            Some(SlotState::Failed) => {
453                self.free_cid(cid);
454                Err(BlkError::Io)
455            }
456            Some(SlotState::Free) | None => Err(BlkError::InvalidRequest),
457        }
458    }
459}
460
461fn alloc_prp_lists(nvme: &Nvme, depth: usize) -> NvmeResult<Vec<CoherentArray<u64>>> {
462    let mut lists = Vec::with_capacity(depth);
463    for _ in 0..depth {
464        lists.push(nvme.alloc_prp_list()?);
465    }
466    Ok(lists)
467}
468
469fn push_prp_pages(
470    pages: &mut Vec<u64>,
471    mut addr: u64,
472    mut len: usize,
473    page_size: usize,
474) -> Result<(), BlkError> {
475    if page_size == 0 || len == 0 {
476        return Err(BlkError::InvalidRequest);
477    }
478
479    while len > 0 {
480        pages.push(addr);
481        let offset = addr as usize % page_size;
482        let chunk = page_size.saturating_sub(offset).min(len);
483        if chunk == 0 {
484            return Err(BlkError::InvalidRequest);
485        }
486        addr = addr
487            .checked_add(chunk as u64)
488            .ok_or(BlkError::InvalidRequest)?;
489        len -= chunk;
490    }
491    Ok(())
492}
493
494fn device_info(name: &'static str, namespace: Namespace) -> DeviceInfo {
495    DeviceInfo {
496        name: Some(name),
497        model: Some("nvme"),
498        ..DeviceInfo::new(namespace.lba_count as u64, namespace.lba_size)
499    }
500}
501
502fn limits(dma_mask: u64, page_size: usize, namespace: Namespace) -> QueueLimits {
503    let prp_entries = page_size / core::mem::size_of::<u64>();
504    let max_bytes = page_size.saturating_mul(prp_entries + 1);
505    let max_blocks = max_bytes
506        .checked_div(namespace.lba_size.max(1))
507        .unwrap_or(1)
508        .max(1)
509        .min(u16::MAX as usize + 1) as u32;
510    QueueLimits {
511        dma_mask,
512        dma_alignment: namespace.lba_size.max(1),
513        max_blocks_per_request: max_blocks,
514        max_segments: prp_entries + 1,
515        max_segment_size: max_bytes,
516        supported_flags: RequestFlags::NONE,
517        supports_flush: true,
518        supports_discard: false,
519        supports_write_zeroes: false,
520    }
521}