From 2770281213a75e8df83f05743dac86b46e391da0 Mon Sep 17 00:00:00 2001
From: Mark Poliakov <mark@alnyan.me>
Date: Fri, 8 Dec 2023 22:49:33 +0200
Subject: [PATCH] dev/block: create io cq/sq pair for nvme

---
 src/device/nvme/command.rs | 75 +++++++++++++++++++++++++++++++++---
 src/device/nvme/mod.rs     | 78 ++++++++++++++++++++++++++++----------
 src/device/nvme/queue.rs   | 35 ++++++++++++-----
 3 files changed, 154 insertions(+), 34 deletions(-)

diff --git a/src/device/nvme/command.rs b/src/device/nvme/command.rs
index 893ee927..9f6231f4 100644
--- a/src/device/nvme/command.rs
+++ b/src/device/nvme/command.rs
@@ -1,6 +1,9 @@
 use core::fmt::{self, Write};
 
-use bytemuck::{Pod, Zeroable};
+use crate::{
+    device::nvme::queue::PhysicalRegionPage,
+    mem::{address::IntoRaw, PhysicalAddress},
+};
 
 use super::queue::SubmissionQueueEntry;
 
@@ -28,6 +31,35 @@ pub enum ControllerType {
     Administrative,
 }
 
+// Requests
+
+#[derive(Clone, Copy, Debug)]
+pub enum SetFeatureRequest {
+    NumberOfQueues(u32, u32),
+}
+
+#[derive(Clone, Copy, Debug)]
+pub struct IdentifyControllerRequest {
+    pub nsid: u32,
+}
+
+#[derive(Clone, Copy, Debug)]
+pub struct CreateIoCompletionQueue {
+    pub id: u32,
+    pub size: usize,
+    pub data: PhysicalAddress,
+}
+
+#[derive(Clone, Copy, Debug)]
+pub struct CreateIoSubmissionQueue {
+    pub id: u32,
+    pub cq_id: u32,
+    pub size: usize,
+    pub data: PhysicalAddress,
+}
+
+// Replies
+
 #[derive(Clone, Copy, Debug)]
 #[repr(C)]
 pub struct IdentifyControllerResponse {
@@ -46,11 +78,6 @@ pub struct IdentifyControllerResponse {
     pub cntrltype: ControllerType,
 }
 
-#[derive(Clone, Copy, Debug)]
-pub struct IdentifyControllerRequest {
-    pub nsid: u32,
-}
-
 impl Command for IdentifyControllerRequest {
     fn fill_sqe(&self, sqe: &mut SubmissionQueueEntry) {
         sqe.command.set_opcode(0x06);
@@ -63,6 +90,42 @@ impl Request for IdentifyControllerRequest {
     type Response = IdentifyControllerResponse;
 }
 
+impl Command for SetFeatureRequest {
+    fn fill_sqe(&self, sqe: &mut SubmissionQueueEntry) {
+        sqe.command.set_opcode(0x09);
+
+        match self {
+            Self::NumberOfQueues(cq, sq) => {
+                let dw11 = (cq << 16) | sq;
+
+                sqe.command_specific[0] = 0x07;
+                sqe.command_specific[1] = dw11;
+            }
+        }
+    }
+}
+
+impl Command for CreateIoCompletionQueue {
+    fn fill_sqe(&self, sqe: &mut SubmissionQueueEntry) {
+        sqe.command.set_opcode(0x05);
+        sqe.data_pointer[1] = PhysicalRegionPage::with_addr(self.data.into_raw());
+        sqe.command_specific[0] = ((self.size as u32 - 1) << 16) | self.id;
+        sqe.command_specific[1] = 1;
+        // TODO ENABLE IRQS HERE
+        sqe.command_specific[2] = 0;
+    }
+}
+
+impl Command for CreateIoSubmissionQueue {
+    fn fill_sqe(&self, sqe: &mut SubmissionQueueEntry) {
+        sqe.command.set_opcode(0x01);
+        sqe.data_pointer[1] = PhysicalRegionPage::with_addr(self.data.into_raw());
+        sqe.command_specific[0] = ((self.size as u32 - 1) << 16) | self.id;
+        // Medium priority
+        sqe.command_specific[1] = (self.cq_id << 16) | 1;
+    }
+}
+
 impl<const N: usize> fmt::Debug for String<N> {
     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
         f.write_char('"')?;
diff --git a/src/device/nvme/mod.rs b/src/device/nvme/mod.rs
index f76cae88..67548164 100644
--- a/src/device/nvme/mod.rs
+++ b/src/device/nvme/mod.rs
@@ -1,11 +1,10 @@
 #![allow(missing_docs)]
-use core::{mem::size_of, ptr::null_mut, time::Duration};
+use core::{mem::size_of, time::Duration};
 
 use abi::error::Error;
-use bytemuck::{Pod, Zeroable};
+use alloc::vec::Vec;
 use device_api::Device;
 use kernel_util::{sync::IrqSafeSpinlock, util::OneTimeInit};
-use static_assertions::{const_assert, const_assert_eq};
 use tock_registers::{
     interfaces::{ReadWriteable, Readable, Writeable},
     register_bitfields, register_structs,
@@ -13,8 +12,6 @@ use tock_registers::{
 };
 
 use crate::{
-    arch::{x86_64::mem::table::L3, Architecture, ARCHITECTURE},
-    debug,
     device::{
         bus::pci::{PciBaseAddress, PciCommandRegister, PciConfigurationSpace},
         nvme::{
@@ -23,17 +20,17 @@ use crate::{
         },
     },
     mem::{
-        address::{AsPhysicalAddress, FromRaw, IntoRaw},
+        address::{FromRaw, IntoRaw},
         device::DeviceMemoryIo,
-        phys,
-        pointer::{PhysicalRef, PhysicalRefMut},
-        table::EntryLevel,
         PhysicalAddress,
     },
     task::runtime,
 };
 
-use self::queue::QueuePair;
+use self::{
+    command::{CreateIoCompletionQueue, CreateIoSubmissionQueue, SetFeatureRequest},
+    queue::QueuePair,
+};
 
 use super::bus::pci::{FromPciBus, PciDeviceInfo};
 
@@ -109,10 +106,13 @@ register_structs! {
 pub struct NvmeController {
     regs: IrqSafeSpinlock<DeviceMemoryIo<'static, Regs>>,
     admin_q: OneTimeInit<QueuePair<'static>>,
+    ioqs: OneTimeInit<Vec<QueuePair<'static>>>,
+
+    doorbell_shift: usize,
 }
 
 impl Regs {
-    unsafe fn doorbell_ptr(&self, shift: u64, completion: bool, queue_index: usize) -> *mut u32 {
+    unsafe fn doorbell_ptr(&self, shift: usize, completion: bool, queue_index: usize) -> *mut u32 {
         let doorbell_base = (self as *const Regs as *mut Regs).addr() + 0x1000;
         let offset = (queue_index << shift) + completion as usize * 4;
         (doorbell_base + offset) as *mut u32
@@ -121,19 +121,49 @@ impl Regs {
 
 impl NvmeController {
     async fn late_init(&'static self) {
-        runtime::spawn(self.poll_task()).expect("Couldn't spawn NVMe poll task");
+        // let ioq = QueuePair::new(capacity, sq_doorbell, cq_doorbell).unwrap();
 
+        runtime::spawn(self.poll_task()).expect("Couldn't spawn NVMe poll task");
         let admin_q = self.admin_q.get();
 
-        let response = admin_q
+        // Request a CQ/SQ pair for I/O
+        admin_q
+            .request_no_data(SetFeatureRequest::NumberOfQueues(1, 1))
+            .unwrap()
+            .await
+            .unwrap();
+
+        // Allocate the queue
+        let (sq_doorbell, cq_doorbell) = unsafe { self.doorbell_pair(1) };
+        let io_q = QueuePair::new(32, sq_doorbell, cq_doorbell).unwrap();
+
+        // Identify the controller
+        let identify = admin_q
             .request(IdentifyControllerRequest { nsid: 0 })
             .unwrap()
             .await
             .unwrap();
 
-        infoln!("Model: {:#?}", response.model_number);
-        infoln!("Serial: {:#?}", response.serial_number);
-        infoln!("Type: {:?}", response.cntrltype);
+        // Create the queue on the device side
+        admin_q
+            .request_no_data(CreateIoCompletionQueue {
+                id: 1,
+                size: 32,
+                data: io_q.cq_physical_pointer(),
+            })
+            .unwrap()
+            .await
+            .unwrap();
+        admin_q
+            .request_no_data(CreateIoSubmissionQueue {
+                id: 1,
+                cq_id: 1,
+                size: 32,
+                data: io_q.sq_physical_pointer(),
+            })
+            .unwrap()
+            .await
+            .unwrap();
 
         loop {}
     }
@@ -145,6 +175,13 @@ impl NvmeController {
             runtime::sleep(Duration::from_millis(100)).await;
         }
     }
+
+    unsafe fn doorbell_pair(&self, idx: usize) -> (*mut u32, *mut u32) {
+        let regs = self.regs.lock();
+        let sq_ptr = regs.doorbell_ptr(self.doorbell_shift, false, idx);
+        let cq_ptr = regs.doorbell_ptr(self.doorbell_shift, true, idx);
+        (sq_ptr, cq_ptr)
+    }
 }
 
 impl Device for NvmeController {
@@ -174,9 +211,8 @@ impl Device for NvmeController {
         }
 
         // Setup the admin queue (index 0)
-        let doorbell_shift = regs.CAP.read(CAP::DSTRD) + 2;
-        let admin_sq_doorbell = unsafe { regs.doorbell_ptr(doorbell_shift, false, 0) };
-        let admin_cq_doorbell = unsafe { regs.doorbell_ptr(doorbell_shift, true, 0) };
+        let admin_sq_doorbell = unsafe { regs.doorbell_ptr(self.doorbell_shift, false, 0) };
+        let admin_cq_doorbell = unsafe { regs.doorbell_ptr(self.doorbell_shift, true, 0) };
         let admin_q =
             QueuePair::new(queue_slots as usize, admin_sq_doorbell, admin_cq_doorbell).unwrap();
 
@@ -238,9 +274,13 @@ impl FromPciBus for NvmeController {
         // Disable the controller
         regs.CC.modify(CC::ENABLE::CLEAR);
 
+        let doorbell_shift = regs.CAP.read(CAP::DSTRD) as usize + 2;
+
         Ok(Self {
             regs: IrqSafeSpinlock::new(regs),
             admin_q: OneTimeInit::new(),
+            ioqs: OneTimeInit::new(),
+            doorbell_shift,
         })
     }
 }
diff --git a/src/device/nvme/queue.rs b/src/device/nvme/queue.rs
index 06c7ec67..7cece0c1 100644
--- a/src/device/nvme/queue.rs
+++ b/src/device/nvme/queue.rs
@@ -18,14 +18,7 @@ use static_assertions::const_assert;
 
 use crate::{
     arch::x86_64::mem::table::L3,
-    mem::{
-        address::{AsPhysicalAddress, IntoRaw},
-        phys,
-        pointer::PhysicalRefMut,
-        table::EntryLevel,
-        PhysicalAddress,
-    },
-    proc,
+    mem::{address::IntoRaw, phys, pointer::PhysicalRefMut, table::EntryLevel, PhysicalAddress},
     task::runtime::QueueWaker,
 };
 
@@ -313,7 +306,7 @@ impl<'a> QueuePair<'a> {
             this: &'r QueuePair<'r>,
             response: Option<R>,
             command_id: u32,
-        };
+        }
 
         impl<'r, R: Unpin + 'r> Future for Fut<'r, R> {
             type Output = Result<R, CommandError>;
@@ -354,6 +347,18 @@ impl<'a> QueuePair<'a> {
         let mut inner = self.inner.lock();
         let mut sqe = SubmissionQueueEntry::zeroed();
 
+        match ranges.len() {
+            1 => {
+                sqe.data_pointer[0] = PhysicalRegionPage::with_addr(ranges[0].into_raw());
+                sqe.data_pointer[1] = PhysicalRegionPage::null();
+            }
+            0 => {
+                sqe.data_pointer[0] = PhysicalRegionPage::null();
+                sqe.data_pointer[1] = PhysicalRegionPage::null();
+            }
+            _ => todo!(),
+        }
+
         cmd.fill_sqe(&mut sqe);
 
         let command_id = inner.sq.tail.try_into().unwrap();
@@ -368,6 +373,17 @@ impl<'a> QueuePair<'a> {
         Ok(command_id)
     }
 
+    pub fn request_no_data<'r, C: Command>(
+        &'r self,
+        req: C,
+    ) -> Result<impl Future<Output = Result<(), CommandError>> + 'r, Error>
+    where
+        'r: 'a,
+    {
+        let command_id = self.submit(req, &[], true)?;
+        Ok(self.wait_for_completion(command_id, ()))
+    }
+
     pub fn request<'r, R: Request>(
         &'r self,
         req: R,
@@ -376,6 +392,7 @@ impl<'a> QueuePair<'a> {
         R::Response: 'r,
         'r: 'a,
     {
+        assert_ne!(size_of::<R::Response>(), 0);
         assert!(size_of::<R::Response>() < 0x1000);
 
         let page = phys::alloc_page()?;