grub-devel
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[PATCH] Implement NVMe native disk support


From: Vladimir Serbinenko
Subject: [PATCH] Implement NVMe native disk support
Date: Thu, 16 May 2024 21:49:44 +0300

This is useful 2-fold:
1) On coreboot port it allows to boot from NVMe devices
2) On older systems you can install NVMe via PCIe adapter and boot from
   it

Signed-off-by: Vladimir Serbinenko <phcoder@gmail.com>
---
 grub-core/Makefile.core.def     |   6 +
 grub-core/commands/nativedisk.c |   3 +-
 grub-core/disk/nvme.c           | 642 ++++++++++++++++++++++++++++++++
 include/grub/disk.h             |   1 +
 4 files changed, 651 insertions(+), 1 deletion(-)
 create mode 100644 grub-core/disk/nvme.c

diff --git a/grub-core/Makefile.core.def b/grub-core/Makefile.core.def
index 8e1b1d9f3..47ac8bf3f 100644
--- a/grub-core/Makefile.core.def
+++ b/grub-core/Makefile.core.def
@@ -1305,6 +1305,12 @@ module = {
   enable = pci;
 };
 
+module = {
+  name = nvme;
+  common = disk/nvme.c;
+  enable = pci;
+};
+
 module = {
   name = pata;
   common = disk/pata.c;
diff --git a/grub-core/commands/nativedisk.c b/grub-core/commands/nativedisk.c
index 580c8d3b0..a431a066a 100644
--- a/grub-core/commands/nativedisk.c
+++ b/grub-core/commands/nativedisk.c
@@ -34,7 +34,7 @@ GRUB_MOD_LICENSE ("GPLv3+");
 static const char *modnames_def[] = {
   /* FIXME: autogenerate this.  */
 #if defined (__i386__) || defined (__x86_64__) || defined 
(GRUB_MACHINE_MIPS_LOONGSON)
-  "pata", "ahci", "usbms", "ohci", "uhci", "ehci"
+  "pata", "ahci", "usbms", "ohci", "uhci", "ehci", "nvme"
 #elif defined (GRUB_MACHINE_MIPS_QEMU_MIPS)
   "pata"
 #else
@@ -77,6 +77,7 @@ get_uuid (const char *name, char **uuid, int getnative)
       /* Native disks.  */
     case GRUB_DISK_DEVICE_ATA_ID:
     case GRUB_DISK_DEVICE_SCSI_ID:
+    case GRUB_DISK_DEVICE_NVME_ID:
     case GRUB_DISK_DEVICE_XEN:
       if (getnative)
        break;
diff --git a/grub-core/disk/nvme.c b/grub-core/disk/nvme.c
new file mode 100644
index 000000000..b2949308d
--- /dev/null
+++ b/grub-core/disk/nvme.c
@@ -0,0 +1,642 @@
+/*
+ *  GRUB  --  GRand Unified Bootloader
+ *
+ *  Copyright (C) 2019 secunet Security Networks AG
+ *  Copyright (C) 2024  Free Software Foundation, Inc.
+ *
+ *  GRUB is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  Additionally this file can be distributed under 3-clause BSD license.
+ *
+ *  GRUB is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with GRUB.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <grub/dl.h>
+#include <grub/disk.h>
+#include <grub/mm.h>
+#include <grub/time.h>
+#include <grub/pci.h>
+#include <grub/misc.h>
+#include <grub/list.h>
+#include <grub/loader.h>
+
+GRUB_MOD_LICENSE ("GPLv3+");
+
+#define NVME_CC_EN     (1 <<  0)
+#define NVME_CC_CSS    (0 <<  4)
+#define NVME_CC_MPS    (0 <<  7)
+#define NVME_CC_AMS    (0 << 11)
+#define NVME_CC_SHN    (0 << 14)
+#define NVME_CC_IOSQES (6 << 16)
+#define NVME_CC_IOCQES (4 << 20)
+
+#define NVME_QUEUE_SIZE 2
+#define NVME_SQ_ENTRY_SIZE 64
+#define NVME_CQ_ENTRY_SIZE 16
+
+struct grub_nvme_mmio_reg
+{
+  /*  0 */ grub_uint64_t cap;
+  /*  8 */ grub_uint32_t vs;
+  /*  c */ grub_uint32_t intms;
+  /* 10 */ grub_uint32_t intmc;
+  /* 14 */ grub_uint32_t controller_config;
+  /* 18 */ grub_uint32_t reserved1;
+  /* 1c */ grub_uint32_t controller_status;
+  /* 20 */ grub_uint32_t nssr;
+  /* 24 */ grub_uint32_t aqa;
+  /* 28 */ grub_uint64_t asq;
+  /* 30 */ grub_uint64_t acq;
+};
+
+struct nvme_ident_block
+{
+  /*  0 */ grub_uint64_t nsze;
+  /*  8 */ grub_uint64_t ncap;
+  /* 10 */ grub_uint64_t nuse;
+  /* 18 */ grub_uint8_t nsfeat;
+  /* 19 */ grub_uint8_t nlbaf;
+  /* 1a */ grub_uint8_t flbas;
+  /* 1b */ grub_uint8_t mc;
+  /* 1c */ grub_uint32_t fill[(0x80 - 0x1c) / 4];
+  /* 80 */ grub_uint32_t lbaf[64];
+};
+
+struct grub_nvme_device
+{
+  struct grub_nvme_device *next;
+  struct grub_nvme_device **prev;
+  volatile struct grub_nvme_mmio_reg *regs;
+  struct grub_pci_dma_chunk *prp_list;
+  struct grub_pci_dma_chunk *sq_buffer;
+  struct grub_pci_dma_chunk *cq_buffer;
+  struct grub_pci_dma_chunk *ioc_buffer;
+  struct grub_pci_dma_chunk *ios_buffer;
+  struct grub_pci_dma_chunk *ident_buffer;
+  int num;
+  grub_uint64_t total_sectors;
+  int log_sector_size;
+
+  struct {
+    volatile void *base;
+    volatile grub_uint32_t *bell;
+    grub_uint16_t idx; // bool pos 0 or 1
+    grub_uint16_t round; // bool round 0 or 1+0xd
+  } queue[4];
+};
+
+struct nvme_s_queue_entry {
+  grub_uint32_t dw[16];
+};
+
+struct nvme_c_queue_entry {
+  grub_uint32_t dw[4];
+};
+
+static struct grub_nvme_device *grub_nvme_devices;
+static int numdevs;
+
+enum nvme_queue {
+  NVME_ADMIN_QUEUE = 0,
+  ads = 0,
+  adc = 1,
+  NVME_IO_QUEUE = 2,
+  ios = 2,
+  ioc = 3,
+};
+
+static int
+nvme_cmd(struct grub_nvme_device *nvme, enum nvme_queue q, const struct 
nvme_s_queue_entry *cmd)
+{
+  int sq = q, cq = q+1;
+
+  void *s_entry = (char *) nvme->queue[sq].base + (nvme->queue[sq].idx * 
NVME_SQ_ENTRY_SIZE);
+  grub_memcpy(s_entry, cmd, NVME_SQ_ENTRY_SIZE);
+  nvme->queue[sq].idx = (nvme->queue[sq].idx + 1) & (NVME_QUEUE_SIZE - 1);
+  *nvme->queue[sq].bell = nvme->queue[sq].idx;
+
+  struct nvme_c_queue_entry *c_entry = (struct nvme_c_queue_entry *)
+    ((char *) nvme->queue[cq].base + (nvme->queue[cq].idx * 
NVME_CQ_ENTRY_SIZE));
+  grub_uint64_t endtime = grub_get_time_ms () + 100;
+  while (((*(volatile grub_uint32_t *)(&c_entry->dw[3]) >> 16) & 0x1) == 
nvme->queue[cq].round)
+    {
+      if (grub_get_time_ms () > endtime)
+       {
+         grub_dprintf("nvme", "command timed out");
+         return -1;
+       }
+    }
+  nvme->queue[cq].idx = (nvme->queue[cq].idx + 1) & (NVME_QUEUE_SIZE - 1);
+  *nvme->queue[cq].bell = nvme->queue[cq].idx;
+  if (nvme->queue[cq].idx == 0)
+    nvme->queue[cq].round = (nvme->queue[cq].round + 1) & 1;
+  return c_entry->dw[3] >> 17;
+}
+
+static int
+create_admin_queues(struct grub_nvme_device *nvme)
+{
+  grub_uint8_t cap_dstrd = (nvme->regs->cap >> 32) & 0xf;
+  nvme->regs->aqa = (NVME_QUEUE_SIZE - 1) << 16 | (NVME_QUEUE_SIZE - 1);
+
+  nvme->sq_buffer = grub_memalign_dma32(0x1000, NVME_SQ_ENTRY_SIZE * 
NVME_QUEUE_SIZE);
+  if (!nvme->sq_buffer)
+    {
+      grub_dprintf("nvme", "NVMe ERROR: Failed to allocated memory for admin 
submission queue\n");
+      return -1;
+    }
+  grub_memset((void *) grub_dma_get_virt(nvme->sq_buffer), 0, 
NVME_SQ_ENTRY_SIZE * NVME_QUEUE_SIZE);
+  nvme->regs->asq = grub_dma_get_phys(nvme->sq_buffer);
+
+  nvme->queue[ads].base = grub_dma_get_virt(nvme->sq_buffer);
+  nvme->queue[ads].bell = (volatile grub_uint32_t *) nvme->regs + 0x1000 / 4 + 
(ads * (1 << cap_dstrd));
+  nvme->queue[ads].idx = 0;
+
+  nvme->cq_buffer = grub_memalign_dma32(0x1000, NVME_CQ_ENTRY_SIZE * 
NVME_QUEUE_SIZE);
+  if (!nvme->cq_buffer)
+    {
+      grub_dprintf("nvme", "NVMe ERROR: Failed to allocate memory for admin 
completion queue\n");
+      grub_dma_free(nvme->sq_buffer);
+      return -1;
+    }
+  grub_memset((void *) grub_dma_get_virt(nvme->cq_buffer), 0, 
NVME_CQ_ENTRY_SIZE * NVME_QUEUE_SIZE);
+  nvme->regs->acq = grub_dma_get_phys(nvme->cq_buffer);
+
+  nvme->queue[adc].base = nvme->cq_buffer;
+  nvme->queue[adc].bell = (volatile grub_uint32_t *) nvme->regs + 0x1000 / 4 + 
(adc * (1 << cap_dstrd));
+  nvme->queue[adc].idx = 0;
+  nvme->queue[adc].round = 0;
+
+  return 0;
+}
+
+static int create_io_submission_queue(struct grub_nvme_device *nvme)
+{
+  nvme->ios_buffer = grub_memalign_dma32(0x1000, NVME_SQ_ENTRY_SIZE * 
NVME_QUEUE_SIZE);
+  if (!nvme->ios_buffer)
+    {
+      grub_dprintf("nvme", "NVMe ERROR: Failed to allocate memory for io 
submission queue.\n");
+      return -1;
+    }
+  grub_memset((void *) grub_dma_get_virt(nvme->ios_buffer), 0, 
NVME_SQ_ENTRY_SIZE * NVME_QUEUE_SIZE);
+
+  struct nvme_s_queue_entry e = {
+    .dw[0]  = 0x01,
+    .dw[6]  = grub_dma_get_phys(nvme->ios_buffer),
+    .dw[10] = ((NVME_QUEUE_SIZE - 1) << 16) | ios >> 1,
+    .dw[11] = (1 << 16) | 1,
+  };
+
+  int res = nvme_cmd(nvme, NVME_ADMIN_QUEUE, &e);
+  if (res) {
+    grub_dprintf("nvme", "NVMe ERROR: nvme_cmd returned with %i.\n", res);
+    grub_dma_free(nvme->ios_buffer);
+    return res;
+  }
+
+  grub_uint8_t cap_dstrd = (nvme->regs->cap >> 32) & 0xf;
+  nvme->queue[ios].base = nvme->ios_buffer;
+  nvme->queue[ios].bell = (volatile grub_uint32_t *) nvme->regs + 0x1000 / 4 + 
(ios * (1 << cap_dstrd));
+  nvme->queue[ios].idx = 0;
+  return 0;
+}
+
+static int create_io_completion_queue(struct grub_nvme_device *nvme)
+{
+  nvme->ioc_buffer = grub_memalign_dma32(0x1000, NVME_CQ_ENTRY_SIZE * 
NVME_QUEUE_SIZE);
+  if (!nvme->ioc_buffer) {
+    grub_dprintf("nvme", "NVMe ERROR: Failed to allocate memory for io 
completion queue.\n");
+    return -1;
+  }
+  grub_memset((void *) grub_dma_get_virt(nvme->ioc_buffer), 0, 
NVME_CQ_ENTRY_SIZE * NVME_QUEUE_SIZE);
+
+  const struct nvme_s_queue_entry e = {
+    .dw[0]  = 0x05,
+    .dw[6]  = grub_dma_get_phys(nvme->ioc_buffer),
+    .dw[10] = ((NVME_QUEUE_SIZE - 1) << 16) | ioc >> 1,
+    .dw[11] = 1,
+  };
+
+  int res = nvme_cmd(nvme, NVME_ADMIN_QUEUE, &e);
+  if (res)
+    {
+      grub_dprintf("nvme", "NVMe ERROR: nvme_cmd returned with %i.\n", res);
+      grub_dma_free(nvme->ioc_buffer);
+      return res;
+    }
+
+  grub_uint8_t cap_dstrd = (nvme->regs->cap >> 32) & 0xf;
+  nvme->queue[ioc].base  = nvme->ioc_buffer;
+  nvme->queue[ioc].bell  = (volatile grub_uint32_t *) nvme->regs + 0x1000 / 4 
+ (ioc * (1 << cap_dstrd));
+  nvme->queue[ioc].idx   = 0;
+  nvme->queue[ioc].round = 0;
+
+  return 0;
+}
+
+static int identify(struct grub_nvme_device *nvme)
+{
+  nvme->ident_buffer = grub_memalign_dma32(0x1000, 0x1000);
+  if (!nvme->ident_buffer) {
+    grub_dprintf("nvme", "NVMe ERROR: Failed to allocate ident buffer.\n");
+    return -1;
+  }
+  grub_memset((void *) grub_dma_get_virt(nvme->ident_buffer), 0, 0x1000);
+
+  const struct nvme_s_queue_entry e = {
+    .dw[0]  = 0x06,
+    .dw[1]  = 0x01,
+    .dw[2]  = 0x00,
+    .dw[6]  = grub_dma_get_phys(nvme->ident_buffer),
+    .dw[10] = 0x00,
+  };
+
+  int res = nvme_cmd(nvme, NVME_ADMIN_QUEUE, &e);
+  if (res)
+    {
+      grub_dprintf("nvme", "NVMe ERROR: nvme_cmd returned with %i.\n", res);
+      grub_memset((void *) grub_dma_get_virt(nvme->ident_buffer), 0, 0x1000);
+      return res;
+    }
+
+  struct nvme_ident_block *nvme_ident = (void *) 
grub_dma_get_virt(nvme->ident_buffer);
+  nvme->total_sectors = nvme_ident->nsze;
+  int selected_lbaf = nvme_ident->flbas & 0xf;
+  nvme->log_sector_size = (nvme_ident->lbaf[selected_lbaf] >> 16) & 0xff;
+
+  grub_dprintf("nvme", "Detected disk with %lld sectors of 2^%d bytes each\n", 
(long long) nvme->total_sectors, nvme->log_sector_size);
+
+  return 0;
+}
+
+static int delete_io_submission_queue(struct grub_nvme_device *nvme)
+{
+       const struct nvme_s_queue_entry e = {
+               .dw[0]  = 0,
+               .dw[10] = ios,
+       };
+
+       int res = nvme_cmd(nvme, NVME_ADMIN_QUEUE, &e);
+
+       grub_dma_free(nvme->ios_buffer);
+       nvme->queue[ios].base = NULL;
+       nvme->queue[ios].bell = NULL;
+       nvme->queue[ios].idx  = 0;
+       return res;
+}
+
+static int delete_io_completion_queue(struct grub_nvme_device *nvme)
+{
+       const struct nvme_s_queue_entry e = {
+               .dw[0]  = 1,
+               .dw[10] = ioc,
+       };
+
+       int res = nvme_cmd(nvme, NVME_ADMIN_QUEUE, &e);
+       grub_dma_free(nvme->ioc_buffer);
+
+       nvme->queue[ioc].base  = NULL;
+       nvme->queue[ioc].bell  = NULL;
+       nvme->queue[ioc].idx   = 0;
+       nvme->queue[ioc].round = 0;
+       return res;
+}
+
+static int
+grub_nvme_pciinit (grub_pci_device_t dev,
+                  grub_pci_id_t pciid __attribute__ ((unused)),
+                  void *data __attribute__ ((unused)))
+{
+  grub_pci_address_t addr;
+  grub_uint32_t class;
+  grub_uint64_t bar;
+  volatile struct grub_nvme_mmio_reg *mmio_reg;
+
+  /* Read class.  */
+  addr = grub_pci_make_address (dev, GRUB_PCI_REG_CLASS);
+  class = grub_pci_read (addr);
+
+  /* Check if this class ID matches that of a PCI NVMe Controller.  */
+  if (class >> 8 != 0x010802)
+    return 0;
+
+  addr = grub_pci_make_address (dev, GRUB_PCI_REG_ADDRESS_REG0);
+
+  bar = grub_pci_read (addr);
+
+  if ((bar & (GRUB_PCI_ADDR_SPACE_MASK | GRUB_PCI_ADDR_MEM_TYPE_MASK
+             | GRUB_PCI_ADDR_MEM_PREFETCH))
+      != (GRUB_PCI_ADDR_SPACE_MEMORY | GRUB_PCI_ADDR_MEM_TYPE_64))
+    return 0;
+
+  addr = grub_pci_make_address (dev, GRUB_PCI_REG_ADDRESS_REG1);
+
+  bar |= ((grub_uint64_t) grub_pci_read (addr)) << 32;
+
+  addr = grub_pci_make_address (dev, GRUB_PCI_REG_COMMAND);
+  grub_pci_write_word (addr, grub_pci_read_word (addr)
+                   | GRUB_PCI_COMMAND_MEM_ENABLED);
+
+  mmio_reg = grub_pci_device_map_range (dev, bar & ~0xfULL,
+                                  sizeof (*mmio_reg));
+  grub_dprintf ("nvme", "dev: %x:%x.%x\n", dev.bus, dev.device, dev.function);
+
+  if (!(mmio_reg->cap & (1LL << 37))) {
+    grub_dprintf ("nvme", "nvme command set not supported\n");
+    return 0;
+  }
+
+  mmio_reg->controller_config = 0;
+
+  struct grub_nvme_device *nvmedev = grub_malloc (sizeof (*nvmedev));
+  if (!nvmedev)
+    return 0;
+
+  nvmedev->regs = mmio_reg;
+  nvmedev->num = numdevs++;
+
+  grub_int32_t max_timeout_ms = ((mmio_reg->cap >> 24) & 0xff) * 500;
+  grub_int32_t timeout_ms = max_timeout_ms;
+  while (1)
+    {
+      grub_uint8_t status = mmio_reg->controller_status & 0x3;
+      if (status == 0x2)
+       {
+         grub_dprintf("nvme", "NVMe ERROR: Failed to disable controller. FATAL 
ERROR\n");
+         return 0;
+       }
+      if (status == 0)
+       break;
+      if (timeout_ms < 0)
+       {
+         grub_dprintf("nvme", "NVMe ERROR: Failed to disable controller. 
Timeout.\n");
+         return 0;
+       }
+      timeout_ms -= 10;
+      grub_millisleep(10);
+    }
+
+  timeout_ms = max_timeout_ms;
+  if (create_admin_queues(nvmedev))
+    {
+      grub_dprintf("nvme", "NVMe ERROR: Failed to create admin queues. FATAL 
ERROR\n");
+      return 0;
+    }
+
+  mmio_reg->controller_config = NVME_CC_EN | NVME_CC_CSS | NVME_CC_MPS | 
NVME_CC_AMS | NVME_CC_SHN
+    | NVME_CC_IOSQES | NVME_CC_IOCQES;
+  while (1)
+    {
+      grub_uint8_t status = mmio_reg->controller_status & 0x3;
+      if (status == 0x2) {
+       grub_dprintf("nvme", "NVMe ERROR: Failed to enable controller. FATAL 
ERROR\n");
+       mmio_reg->controller_config = 0;
+       return 0;
+      }
+      if (status == 1)
+       break;
+      if (timeout_ms < 0) {
+       grub_dprintf("nvme", "NVMe ERROR: Failed to enable controller. 
Timeout.\n");
+       mmio_reg->controller_config = 0;
+       return 0;
+      }
+      timeout_ms -= 10;
+      grub_millisleep(10);
+    }
+
+  nvmedev->prp_list = grub_memalign_dma32(0x1000, 0x1000);
+  if (!nvmedev->prp_list) {
+    mmio_reg->controller_config = 0;
+    grub_free (nvmedev);
+    return 0;
+  }
+
+  addr = grub_pci_make_address (dev, GRUB_PCI_REG_COMMAND);
+  grub_pci_write_word (addr, grub_pci_read_word (addr) | 
GRUB_PCI_COMMAND_BUS_MASTER);
+
+  create_io_completion_queue(nvmedev);
+  create_io_submission_queue(nvmedev);
+
+  identify(nvmedev);
+  
+  grub_list_push (GRUB_AS_LIST_P (&grub_nvme_devices),
+                 GRUB_AS_LIST (nvmedev));
+  return 0;
+}
+
+static grub_err_t
+grub_nvme_initialize (void)
+{
+  grub_pci_iterate (grub_nvme_pciinit, NULL);
+  return grub_errno;
+}
+
+static grub_err_t
+grub_nvme_fini_hw (int noreturn __attribute__ ((unused)))
+{
+  struct grub_nvme_device *dev;
+
+  for (dev = grub_nvme_devices; dev; dev = dev->next)
+    {
+      delete_io_submission_queue(dev);
+      delete_io_completion_queue(dev);
+      dev->regs->controller_config = 0;
+      /* TODO: wait for completition.  */
+    }
+  return GRUB_ERR_NONE;
+}
+
+
+static grub_err_t
+grub_nvme_restore_hw (void)
+{
+  struct grub_nvme_device **pdev;
+
+  for (pdev = &grub_nvme_devices; *pdev; pdev = &((*pdev)->next))
+    {
+      (*pdev)->regs->controller_config = NVME_CC_EN | NVME_CC_CSS | 
NVME_CC_MPS | NVME_CC_AMS | NVME_CC_SHN
+       | NVME_CC_IOSQES | NVME_CC_IOCQES;
+      create_io_completion_queue(*pdev);
+      create_io_submission_queue(*pdev);
+      /* TODO: Error handling.  */
+    }
+  return GRUB_ERR_NONE;
+}
+
+
+
+
+static int
+grub_nvme_iterate (grub_disk_dev_iterate_hook_t hook, void *hook_data,
+                 grub_disk_pull_t pull)
+{
+  struct grub_nvme_device *dev;
+
+  if (pull != GRUB_DISK_PULL_NONE)
+    return 0;
+
+  FOR_LIST_ELEMENTS(dev, grub_nvme_devices)
+    {
+      char devname[40];
+      /* TODO: Other namespaces.  */
+      grub_snprintf (devname, sizeof (devname),
+                    "nvme%dn%d", dev->num, 1);
+      if (hook (devname, hook_data))
+       return 1;
+    }
+
+  return 0;
+}
+
+static grub_err_t
+grub_nvme_open (const char *name, grub_disk_t disk)
+{
+  const char *rest;
+  if (grub_memcmp(name, "nvme", 4) != 0 || !grub_isdigit(name[4]))
+    return grub_error (GRUB_ERR_UNKNOWN_DEVICE, "not an NVMe disk");
+  int devnum = grub_strtoul (name + 4, &rest, 0);
+  if (*rest != 'n')
+    return grub_error (GRUB_ERR_UNKNOWN_DEVICE, "not an NVMe disk");
+  int namespace = grub_strtoul (rest + 1, 0, 0);
+
+  struct grub_nvme_device *dev;
+
+  FOR_LIST_ELEMENTS(dev, grub_nvme_devices)
+    if (dev->num == devnum)
+      {
+       if (namespace != 1)
+         return grub_error (GRUB_ERR_UNKNOWN_DEVICE, "unknown NVMe namespace");
+
+       disk->total_sectors = dev->total_sectors;
+       disk->max_agglomerate = 512;
+
+       disk->log_sector_size = dev->log_sector_size;
+       disk->id = (devnum << 8) | namespace;
+       disk->data = dev;
+       return 0;
+      }
+
+  return grub_error (GRUB_ERR_UNKNOWN_DEVICE, "unknown NVMe disk");
+}
+
+static void
+grub_nvme_close (grub_disk_t disk)
+{
+  (void) disk;
+}
+
+static grub_err_t
+nvme_readwrite (struct grub_nvme_device *dev, int namespace,
+               grub_disk_addr_t sector, grub_size_t size, char *buf, int 
is_write)
+{
+  (void) namespace;
+
+  if (size == 0)
+    return 0;
+
+  if (size > 512)
+    return grub_error(GRUB_ERR_BAD_ARGUMENT, "overlong nvme read");
+
+  /* This assumes virt == phys which is true on platforms where we support 
nvme.  */
+  grub_uint64_t buffer_phys = (grub_addr_t) buf;
+
+  struct nvme_s_queue_entry e = {
+    .dw[0] = is_write ? 0x01 : 0x02,
+    .dw[1] = 0x1,
+    .dw[6] = (grub_addr_t) buffer_phys,
+    .dw[7] = (grub_addr_t) (buffer_phys >> 32),
+    .dw[10] = sector,
+    .dw[11] = sector >> 32,
+    .dw[12] = size - 1,
+  };
+
+  const grub_uint64_t start_page = buffer_phys >> 12;
+  const grub_uint64_t end_page = (buffer_phys + (size << dev->log_sector_size) 
- 1) >> 12;
+  if (end_page == start_page) {
+    /* No page crossing, PRP2 is reserved */
+  } else if (end_page == start_page + 1) {
+    /* Crossing exactly one page boundary, PRP2 is second page */
+    e.dw[8] = (buffer_phys + 0x1000) & ~0xfff;
+  } else {
+    /* Use a single page as PRP list, PRP2 points to the list */
+    unsigned int i;
+    volatile grub_uint64_t *prp_list = grub_dma_get_virt(dev->prp_list);
+    for (i = 0; i < end_page - start_page; ++i) {
+      buffer_phys += 0x1000;
+      prp_list[i] = buffer_phys & ~0xfff;
+    }
+    e.dw[8] = grub_dma_get_phys(dev->prp_list);
+  }
+
+  int io_err = nvme_cmd(dev, ios, &e);
+  if (io_err)
+    return grub_error(GRUB_ERR_IO, "NVMe error %d", io_err);
+  return GRUB_ERR_NONE;
+}
+
+static grub_err_t
+grub_nvme_read (grub_disk_t disk, grub_disk_addr_t sector,
+               grub_size_t size, char *buf)
+{
+  struct grub_nvme_device *dev = disk->data;
+  int namespace = disk->id & 0xff;
+
+  return nvme_readwrite(dev, namespace, sector, size, buf, 0);
+}
+
+static grub_err_t
+grub_nvme_write (grub_disk_t disk, grub_disk_addr_t sector,
+                grub_size_t size, const char *buf)
+{
+  struct grub_nvme_device *dev = disk->data;
+  int namespace = disk->id & 0xff;
+
+  return nvme_readwrite(dev, namespace, sector, size, (char *)buf, 1);
+}
+
+static struct grub_disk_dev grub_nvme_dev =
+  {
+    .name = "nvme",
+    .id = GRUB_DISK_DEVICE_NVME_ID,
+    .disk_iterate = grub_nvme_iterate,
+    .disk_open = grub_nvme_open,
+    .disk_close = grub_nvme_close,
+    .disk_read = grub_nvme_read,
+    .disk_write = grub_nvme_write,
+    .next = 0
+  };
+
+
+
+static struct grub_preboot *fini_hnd;
+
+GRUB_MOD_INIT(nvme)
+{
+  grub_stop_disk_firmware ();
+
+  /* NVMe initialization.  */
+  grub_nvme_initialize ();
+
+  grub_disk_dev_register (&grub_nvme_dev);
+
+  fini_hnd = grub_loader_register_preboot_hook (grub_nvme_fini_hw,
+                                               grub_nvme_restore_hw,
+                                               
GRUB_LOADER_PREBOOT_HOOK_PRIO_DISK);
+}
+
+GRUB_MOD_FINI(nvme)
+{
+  grub_nvme_fini_hw (0);
+  grub_loader_unregister_preboot_hook (fini_hnd);
+
+  grub_disk_dev_unregister (&grub_nvme_dev);
+}
diff --git a/include/grub/disk.h b/include/grub/disk.h
index fbf23df7f..fce52c207 100644
--- a/include/grub/disk.h
+++ b/include/grub/disk.h
@@ -52,6 +52,7 @@ enum grub_disk_dev_id
     GRUB_DISK_DEVICE_UBOOTDISK_ID,
     GRUB_DISK_DEVICE_XEN,
     GRUB_DISK_DEVICE_OBDISK_ID,
+    GRUB_DISK_DEVICE_NVME_ID,
   };
 
 struct grub_disk;
-- 
2.39.2




reply via email to

[Prev in Thread] Current Thread [Next in Thread]