mirror of
https://codeberg.org/libreboot/lbmk.git
synced 2026-07-17 08:40:14 +02:00
512cce0770
The argon2 patches were dropped (PHC implementation), because GRUB has now its own Argon2 KDF implementation as part of its recent libgcrypt update. Argon2 support is therefore retained, but GNU's implementation has replaced the one that Libreboot previously used. This brings in the following upstream changes: * 280715ec6 Release 2.14~rc1 * e549317e1 windows: Fix symbol table generation during module conversion from PE to ELF * a340750f9 windows: Fix relocation sections generation during module conversion from PE to ELF * c602035a9 loader/efi/linux: Fix compile error with Clang * f62269767 build: Add tpm2key.asn file for reference to dist archive * 46be4488f build: Include new zstd test support files in dist archive * fa79d5ea9 build: Include MAINTAINERS and SECURITY files in dist archive * 8271bcc13 build: Add appended signatures header file to EXTRA_DIST * 11845da2b lib/xzembed/xz_dec_stream: Replace grub_memcpy() call with memcpy() * 7ded35fea bootstrap: Fix patching warnings * 4129e9ad6 tss2: Always init out buffer before calling tpm2_submit_command_real() * 000e48b42 fs/ntfs: Correct next_attribute validation * 5ff9c43cf kern/ieee1275/init: Use net config for boot location instead of firmware bootpath * c2cae77ab net/tftp: Fix NULL pointer dereference in grub_net_udp_close() * fadc94b91 net/dns: Prevent UAF and double free * cd24e2591 net/bootp: Prevent a UAF in network interface unregister * fca6c0afd docs: Document lsmemregions and memtools commands * 42c099786 commands/memtools: Add lsmemregions command * 20211246a tests/file_filter: Add zstd tests * 092449f63 tests/file_filter: Add zstd test file * 356dcac9a tests/file_filter: Regenerate gpg keys * 8ea83c3ee io/zstdio: Implement zstdio decompression * 7c22c1000 fs/btrfs: Update doc link for bootloader support * 6435551a6 docs: Add Btrfs env block and special env vars * b63447748 util/grub.d/00_header.in: Wire grub.cfg to use env_block when present * e4e177661 fs/btrfs: Add environment block to reserved header area * d6525f0e6 util/grub-editenv: Add probe call for external envblk * 4b5ea8dca util/grub-editenv: Wire list_variables() to optional fs_envblk * 84e2bc2f1 util/grub-editenv: Wire unset_variables() to optional fs_envblk * c7c901916 util/grub-editenv: Wire set_variables() to optional fs_envblk * 2abdd8cd2 util/grub-editenv: Add fs_envblk write helper * e4d684cc4 util/grub-editenv: Add fs_envblk open helper * 512e33ec7 tests: Add "z" length modifier printf tests * 3c9762b12 kern/misc: Add the "z" length modifier support * 91ddada64 disk/cryptodisk: Add --hw-accel to enable hardware acceleration * f8f68f14a libgcrypt: Add hardware acceleration for gcry_sha512 * 70b2f5f08 libgcrypt: Add hardware acceleration for gcry_sha256 * 2158d8e8a libgcrypt: Declare the sha256 shaext function * 0ff5faf8c libgcrypt: Implement _gcry_get_hw_features() * 812356191 libgcrypt: Copy sha512 x86_64 assembly files * 7f9c590af libgcrypt: Copy sha256 x86_64 assembly files * 8423176f1 lib/hwfeatures-gcry: Enable SSE and AVX for x86_64 EFI * 06a5b88ba lib/hwfeatures-gcry: Introduce functions to manage hardware features * d01abd713 configure: Tweak autoconf/automake files to detect x86_64 features * a122e0262 lib/pbkdf2: Optimize PBKDF2 by reusing HMAC handle * 961e38b2b lib/crypto: Introduce new HMAC functions to reuse buffers * 59304a7b5 docs: Document argon2 and argon2_test modules * 28dbe8a3b kern/misc: Implement faster grub_memcpy() for aligned buffers * da01eb0c5 tests/util/grub-fs-tester: Use Argon2id for LUKS2 test * c1bd9fc82 tests: Integrate Argon2 tests into functional_test * 6a525ee64 tests: Import Argon2 tests from libgcrypt * 6052fc2cf disk/luks2: Add Argon2 support * 66b8718f9 argon2: Introduce grub_crypto_argon2() * de201105d libgcrypt/kdf: Fix 64-bit modulus on 32-bit platforms * 93544861b libgcrypt/kdf: Remove unsupported KDFs * 1ff720641 libgcrypt/kdf: Get rid of gpg_err_code_from_errno() * 0c06a454f libgcrypt/kdf: Implement hash_buffers() for BLAKE2b-512 * bc94dfd54 crypto: Update crypto.h for libgcrypt KDF functions * 5b81f490c util/import_gcry: Import kdf.c for Argon2 * 6b5c671d3 commands/menuentry: Fix for out of bound access * 21cdcb125 tests/tpm2_key_protector_test: Add a test for PCR Capping * afddba012 tpm2_key_protector: Support PCR capping * ae7a39900 tss2: Implement grub_tcg2_cap_pcr() for emu * 7b39970e9 tss2: Implement grub_tcg2_cap_pcr() for ieee1275 * 39f98e471 tss2: Implement grub_tcg2_cap_pcr() for EFI * d47d261ec tss2: Introduce grub_tcg2_cap_pcr() * b2549b4d3 tss2: Add TPM2_PCR_Event command * e1b9d92a8 loader/i386/linux: Transfer EDID information to kernel * a8379e693 fs/hfsplus: Allow reading files created by Mac OS 9 * c5ff0d616 docs: Fix build warnings in libgcrypt and blsuki doc * fa93f2412 kern/command,commands/extcmd: Perform explicit NULL check in both the unregister helpers * 9a725391f commands/efi/tpm: Call get_active_pcr_banks() only with TCG2 1.1 or newer * 894241c85 kern: Include function name on debug and error print functions * 75a20cc14 kern: Make grub_error() more verbose * 8abbafa49 net/tcp: Fix TCP port number reused on reboot * 3dff10a97 docs/grub: Document appended signature * 0f2dda8cf docs/grub: Document signing GRUB with an appended signature * 0b59d379f docs/grub: Document signing GRUB under UEFI * dbfa3d7d7 appended signatures: Verification tests * 7f68c7195 appended signatures: GRUB commands to manage the hashes * 6cb58b1c9 appended signatures: GRUB commands to manage the certificates * ab7b17717 appended signatures: Using db and dbx lists for signature verification * 97f7001e1 appended signatures: Create db and dbx lists * b5e872417 appended signatures: Introducing key management environment variable * 76158ed1a powerpc/ieee1275: Read the db and dbx secure boot variables * 069f3614e appended signatures: Support verifying appended signatures * f8e8779d8 powerpc/ieee1275: Enter lockdown based on /ibm, secure-boot * e95c52f1f appended signatures: Parse X.509 certificates * a33754979 appended signatures: Parse PKCS#7 signed data * 3e4ff6ffb appended signatures: Parse ASN1 node * 7d28bdb0b appended signatures: Import GNUTLS's ASN.1 description files * 1fca5f397 grub-install: Support embedding x509 certificates * aefe0de22 pgp: Rename OBJ_TYPE_PUBKEY to OBJ_TYPE_GPG_PUBKEY * f826cc8b0 crypto: Move storage for grub_crypto_pk_* to crypto.c * 31cc7dfe5 powerpc/ieee1275: Add support for signing GRUB with an appended signature * ee789e1a6 lib/b64dec: Use grub_size_t instead of size_t for _gpgrt_b64dec_proc() function definition * abb8fb6d1 util/grub-mkimagexx: Fix riscv32 relocation offset * 1f9092bfd libgcrypt: Allow GRUB to build with Clang * 1d2ee8f8b tests: Add test ISO files to dist package * dfa3dbf61 tests: Test dates outside of 32-bit Unix range * 6837293b8 lib/datetime: Support dates outside of 1901..2038 range * 02788bfdf bootstrap: Ensure shallow gnulib clone works on newer git * 4e42199f3 docs: Correct some URLs * 733cc28eb docs: Update Future section to reflect current release * 54c8573ef docs: Document new libgrypt modules * 1562dee69 docs: Clarify section heading and fix wording * cf1b75a14 BUGS: Update to point to bug tracking system * 236663dfb INSTALL: Document libtasn1 needed for grub-protect * 7bfb38627 po: Update translations to build with gettext 0.26 * 49e76ad16 term/efi/console: Treat key.scan_code 0x0102 (suspend) as Enter * de72f3998 util/bash-completion.d/Makefile.am: s/mkrescure/mkrescue/g * 14c2966c7 blsuki: Add uki command to load Unified Kernel Image entries * 5190df851 blsuki: Check for mounted /boot in emu * 51b960132 util/misc.c: Change offset type for grub_util_write_image_at() * 8cee1c284 blsuki: Add blscfg command to parse Boot Loader Specification snippets * e016d6d60 kern/misc: Implement grub_strtok() * 587db89af kern/xen: Add Xen command line parsing * b2a975bc5 include/xen/xen.h: Add warning comment for cmd_line * 19c698d12 zfs: Fix LINUX_ROOT_DEVICE when grub-probe fails * 6898fcf74 relocator: Switch to own page table while moving chunks * 67a95527b configure: Generate tar-ustar tarball instead of tar-v7 * 29d515b4c build: Add new libgcrypt and libtasn1 related files to EXTRA_DISTS * eb76b064d build: Add util/import_gcrypt_inth.sed to EXTRA_DISTS * eb56a6af9 include/xen/xen.h: Rename MAX_GUEST_CMDLINE to GRUB_XEN_MAX_GUEST_CMDLINE * 80df5e132 loader/arm64/xen_boot: Set correctly bootargs property for modules * 6831d242a loader/efi/linux: Return correct size from LoadFile2 * f326c5c47 commands/bli: Set LoaderTpm2ActivePcrBanks runtime variable * 0e367796c docs: Write how to import new libgcrypt * b930bfa37 libgcrypt: Fix a memory leak * d48c277c4 libgcrypt: Don't use 64-bit division on platforms where it's slow * de49514c9 util/import_gcry: Fix pylint warnings * 334353a97 util/import_gcry: Make compatible with Python 3.4 * 2a6de4209 libgcrypt: Import blake family of hashes * e54187912 libgcrypt: Ignore sign-compare warnings * e3b78e49c libgcrypt: Remove now unneeded compilation flag * e23704ad4 libgcrypt: Fix Coverity warnings * d65810b01 keccak: Disable acceleration with SSE asm * f808ef0d2 tests: Add DSA and RSA SEXP tests * 0739d24cd libgcrypt: Adjust import script, definitions and API users for libgcrypt 1.11 * 3e1c2890b b64dec: Add harness for compilation in GRUB environment * 5ca0d5e41 b64dec: Import b64dec from gpg-error * 3312af6e0 libgcrypt: Import libgcrypt 1.11 * a0d4c94ef loader/efi/linux: Use shim loader image handle where available * 1b9a84e63 loader/efi/chainloader: Use shim loader image handle where available * e31d0cd7f efi/sb: Add API for retrieving shim loader image handles * ed7e053a3 efi/sb: Add support for the shim loader protocol * 70897d3d3 efi: Provide wrappers for load_image, start_image and unload_image * 030a70fca loader/arm64/xen_boot: Consider alignment calling grub_arch_efi_linux_boot_image() * e82609a47 loader/arm64/xen_boot: Use bool instead of int * d1a470b69 loader/arm64/xen_boot: Remove correctly all modules loaded by xen_module command * cf5e52fa8 dl: Fix grub_dl_is_persistent() for emu
1075 lines
32 KiB
Diff
1075 lines
32 KiB
Diff
From e390ed303e135f6509889ff1ef3f5e3bcb28e89f Mon Sep 17 00:00:00 2001
|
|
From: Mate Kukri <km@mkukri.xyz>
|
|
Date: Mon, 20 May 2024 11:43:35 +0100
|
|
Subject: [PATCH 8/9] Add native NVMe driver based on SeaBIOS
|
|
|
|
Tested to successfully boot Debian on QEMU and OptiPlex 3050.
|
|
|
|
Signed-off-by: Mate Kukri <km@mkukri.xyz>
|
|
---
|
|
Makefile.am | 2 +-
|
|
grub-core/Makefile.core.def | 6 +
|
|
grub-core/commands/nativedisk.c | 1 +
|
|
grub-core/disk/nvme-int.h | 208 +++++++++
|
|
grub-core/disk/nvme.c | 781 ++++++++++++++++++++++++++++++++
|
|
include/grub/disk.h | 1 +
|
|
6 files changed, 998 insertions(+), 1 deletion(-)
|
|
create mode 100644 grub-core/disk/nvme-int.h
|
|
create mode 100644 grub-core/disk/nvme.c
|
|
|
|
diff --git a/Makefile.am b/Makefile.am
|
|
index 43635d5ff..2c86dbbf6 100644
|
|
--- a/Makefile.am
|
|
+++ b/Makefile.am
|
|
@@ -434,7 +434,7 @@ if COND_i386_coreboot
|
|
FS_PAYLOAD_MODULES ?= $(shell cat grub-core/fs.lst)
|
|
default_payload.elf: grub-mkstandalone grub-mkimage FORCE
|
|
test -f $@ && rm $@ || true
|
|
- pkgdatadir=. ./grub-mkstandalone --grub-mkimage=./grub-mkimage -O i386-coreboot -o $@ --modules='ahci pata ehci uhci ohci usb_keyboard usbms part_msdos ext2 fat at_keyboard part_gpt usbserial_usbdebug cbfs' --install-modules='ls linux search configfile normal cbtime cbls memrw iorw minicmd lsmmap lspci halt reboot hexdump pcidump regexp setpci lsacpi chain test serial multiboot cbmemc linux16 gzio echo help syslinuxcfg xnu $(FS_PAYLOAD_MODULES) password_pbkdf2 $(EXTRA_PAYLOAD_MODULES)' --fonts= --themes= --locales= -d grub-core/ /boot/grub/grub.cfg=$(srcdir)/coreboot.cfg
|
|
+ pkgdatadir=. ./grub-mkstandalone --grub-mkimage=./grub-mkimage -O i386-coreboot -o $@ --modules='ahci pata nvme ehci uhci ohci usb_keyboard usbms part_msdos ext2 fat at_keyboard part_gpt usbserial_usbdebug cbfs' --install-modules='ls linux search configfile normal cbtime cbls memrw iorw minicmd lsmmap lspci halt reboot hexdump pcidump regexp setpci lsacpi chain test serial multiboot cbmemc linux16 gzio echo help syslinuxcfg xnu $(FS_PAYLOAD_MODULES) password_pbkdf2 $(EXTRA_PAYLOAD_MODULES)' --fonts= --themes= --locales= -d grub-core/ /boot/grub/grub.cfg=$(srcdir)/coreboot.cfg
|
|
endif
|
|
|
|
endif
|
|
diff --git a/grub-core/Makefile.core.def b/grub-core/Makefile.core.def
|
|
index fa4bc54aa..c608d0450 100644
|
|
--- a/grub-core/Makefile.core.def
|
|
+++ b/grub-core/Makefile.core.def
|
|
@@ -2768,3 +2768,9 @@ module = {
|
|
cflags = '-Wno-uninitialized';
|
|
cppflags = '-I$(srcdir)/lib/libtasn1-grub -I$(srcdir)/tests/asn1/';
|
|
};
|
|
+
|
|
+module = {
|
|
+ name = nvme;
|
|
+ common = disk/nvme.c;
|
|
+ enable = pci;
|
|
+};
|
|
diff --git a/grub-core/commands/nativedisk.c b/grub-core/commands/nativedisk.c
|
|
index 6806bff9c..fd68a513e 100644
|
|
--- a/grub-core/commands/nativedisk.c
|
|
+++ b/grub-core/commands/nativedisk.c
|
|
@@ -78,6 +78,7 @@ get_uuid (const char *name, char **uuid, int getnative)
|
|
case GRUB_DISK_DEVICE_ATA_ID:
|
|
case GRUB_DISK_DEVICE_SCSI_ID:
|
|
case GRUB_DISK_DEVICE_XEN:
|
|
+ case GRUB_DISK_DEVICE_NVME_ID:
|
|
if (getnative)
|
|
break;
|
|
/* FALLTHROUGH */
|
|
diff --git a/grub-core/disk/nvme-int.h b/grub-core/disk/nvme-int.h
|
|
new file mode 100644
|
|
index 000000000..1295b58aa
|
|
--- /dev/null
|
|
+++ b/grub-core/disk/nvme-int.h
|
|
@@ -0,0 +1,208 @@
|
|
+// NVMe datastructures and constants
|
|
+//
|
|
+// Copyright 2017 Amazon.com, Inc. or its affiliates.
|
|
+//
|
|
+// This file may be distributed under the terms of the GNU LGPLv3 license.
|
|
+
|
|
+#ifndef __NVME_INT_H
|
|
+#define __NVME_INT_H
|
|
+
|
|
+#include <grub/types.h>
|
|
+
|
|
+/* Data structures */
|
|
+
|
|
+/* The register file of a NVMe host controller. This struct follows the naming
|
|
+ scheme in the NVMe specification. */
|
|
+struct nvme_reg {
|
|
+ grub_uint64_t cap; /* controller capabilities */
|
|
+ grub_uint32_t vs; /* version */
|
|
+ grub_uint32_t intms; /* interrupt mask set */
|
|
+ grub_uint32_t intmc; /* interrupt mask clear */
|
|
+ grub_uint32_t cc; /* controller configuration */
|
|
+ grub_uint32_t _res0;
|
|
+ grub_uint32_t csts; /* controller status */
|
|
+ grub_uint32_t _res1;
|
|
+ grub_uint32_t aqa; /* admin queue attributes */
|
|
+ grub_uint64_t asq; /* admin submission queue base address */
|
|
+ grub_uint64_t acq; /* admin completion queue base address */
|
|
+};
|
|
+
|
|
+/* Submission queue entry */
|
|
+struct nvme_sqe {
|
|
+ union {
|
|
+ grub_uint32_t dword[16];
|
|
+ struct {
|
|
+ grub_uint32_t cdw0; /* Command DWORD 0 */
|
|
+ grub_uint32_t nsid; /* Namespace ID */
|
|
+ grub_uint64_t _res0;
|
|
+ grub_uint64_t mptr; /* metadata ptr */
|
|
+
|
|
+ grub_uint64_t dptr_prp1;
|
|
+ grub_uint64_t dptr_prp2;
|
|
+ };
|
|
+ };
|
|
+};
|
|
+
|
|
+/* Completion queue entry */
|
|
+struct nvme_cqe {
|
|
+ union {
|
|
+ grub_uint32_t dword[4];
|
|
+ struct {
|
|
+ grub_uint32_t cdw0;
|
|
+ grub_uint32_t _res0;
|
|
+ grub_uint16_t sq_head;
|
|
+ grub_uint16_t sq_id;
|
|
+ grub_uint16_t cid;
|
|
+ grub_uint16_t status;
|
|
+ };
|
|
+ };
|
|
+};
|
|
+
|
|
+/* The common part of every submission or completion queue. */
|
|
+struct nvme_queue {
|
|
+ grub_uint32_t *dbl; /* doorbell */
|
|
+ grub_uint16_t mask; /* length - 1 */
|
|
+};
|
|
+
|
|
+struct nvme_cq {
|
|
+ struct nvme_queue common;
|
|
+ struct nvme_cqe *cqe;
|
|
+
|
|
+ /* We have read upto (but not including) this entry in the queue. */
|
|
+ grub_uint16_t head;
|
|
+
|
|
+ /* The current phase bit the controller uses to indicate that it has written
|
|
+ a new entry. This is inverted after each wrap. */
|
|
+ unsigned phase : 1;
|
|
+};
|
|
+
|
|
+struct nvme_sq {
|
|
+ struct nvme_queue common;
|
|
+ struct nvme_sqe *sqe;
|
|
+
|
|
+ /* Corresponding completion queue. We only support a single SQ per CQ. */
|
|
+ struct nvme_cq *cq;
|
|
+
|
|
+ /* The last entry the controller has fetched. */
|
|
+ grub_uint16_t head;
|
|
+
|
|
+ /* The last value we have written to the tail doorbell. */
|
|
+ grub_uint16_t tail;
|
|
+};
|
|
+
|
|
+struct nvme_ctrl {
|
|
+ grub_pci_device_t pci;
|
|
+ struct nvme_reg volatile *reg;
|
|
+
|
|
+ grub_uint32_t ctrlnum;
|
|
+
|
|
+ grub_uint32_t doorbell_stride; /* in bytes */
|
|
+
|
|
+ struct nvme_sq admin_sq;
|
|
+ struct nvme_cq admin_cq;
|
|
+
|
|
+ grub_uint32_t ns_count;
|
|
+
|
|
+ struct nvme_sq io_sq;
|
|
+ struct nvme_cq io_cq;
|
|
+};
|
|
+
|
|
+struct nvme_namespace {
|
|
+ struct nvme_namespace *next;
|
|
+ struct nvme_namespace **prev;
|
|
+
|
|
+ char *devname;
|
|
+
|
|
+ grub_uint32_t nsnum;
|
|
+
|
|
+ struct nvme_ctrl *ctrl;
|
|
+
|
|
+ grub_uint32_t ns_id;
|
|
+
|
|
+ grub_uint64_t lba_count; /* The total amount of sectors. */
|
|
+
|
|
+ grub_uint32_t block_size;
|
|
+ grub_uint32_t metadata_size;
|
|
+ grub_uint32_t max_req_size;
|
|
+};
|
|
+
|
|
+/* Data structures for NVMe admin identify commands */
|
|
+
|
|
+struct nvme_identify_ctrl {
|
|
+ grub_uint16_t vid;
|
|
+ grub_uint16_t ssvid;
|
|
+ char sn[20];
|
|
+ char mn[40];
|
|
+ char fr[8];
|
|
+
|
|
+ grub_uint8_t rab;
|
|
+ grub_uint8_t ieee[3];
|
|
+ grub_uint8_t cmic;
|
|
+ grub_uint8_t mdts;
|
|
+
|
|
+ char _boring[516 - 78];
|
|
+
|
|
+ grub_uint32_t nn; /* number of namespaces */
|
|
+};
|
|
+
|
|
+struct nvme_identify_ns_list {
|
|
+ grub_uint32_t ns_id[1024];
|
|
+};
|
|
+
|
|
+struct nvme_lba_format {
|
|
+ grub_uint16_t ms;
|
|
+ grub_uint8_t lbads;
|
|
+ grub_uint8_t rp;
|
|
+};
|
|
+
|
|
+struct nvme_identify_ns {
|
|
+ grub_uint64_t nsze;
|
|
+ grub_uint64_t ncap;
|
|
+ grub_uint64_t nuse;
|
|
+ grub_uint8_t nsfeat;
|
|
+ grub_uint8_t nlbaf;
|
|
+ grub_uint8_t flbas;
|
|
+
|
|
+ char _boring[128 - 27];
|
|
+
|
|
+ struct nvme_lba_format lbaf[16];
|
|
+};
|
|
+
|
|
+union nvme_identify {
|
|
+ struct nvme_identify_ns ns;
|
|
+ struct nvme_identify_ctrl ctrl;
|
|
+ struct nvme_identify_ns_list ns_list;
|
|
+};
|
|
+
|
|
+/* NVMe constants */
|
|
+
|
|
+#define NVME_CAP_CSS_NVME (1ULL << 37)
|
|
+
|
|
+#define NVME_CSTS_FATAL (1U << 1)
|
|
+#define NVME_CSTS_RDY (1U << 0)
|
|
+
|
|
+#define NVME_CC_EN (1U << 0)
|
|
+
|
|
+#define NVME_SQE_OPC_ADMIN_CREATE_IO_SQ 1U
|
|
+#define NVME_SQE_OPC_ADMIN_CREATE_IO_CQ 5U
|
|
+#define NVME_SQE_OPC_ADMIN_IDENTIFY 6U
|
|
+
|
|
+#define NVME_SQE_OPC_IO_WRITE 1U
|
|
+#define NVME_SQE_OPC_IO_READ 2U
|
|
+
|
|
+#define NVME_ADMIN_IDENTIFY_CNS_ID_NS 0U
|
|
+#define NVME_ADMIN_IDENTIFY_CNS_ID_CTRL 1U
|
|
+#define NVME_ADMIN_IDENTIFY_CNS_GET_NS_LIST 2U
|
|
+
|
|
+#define NVME_CQE_DW3_P (1U << 16)
|
|
+
|
|
+#define NVME_PAGE_SIZE 4096
|
|
+#define NVME_PAGE_MASK ~(NVME_PAGE_SIZE - 1)
|
|
+
|
|
+/* Length for the queue entries. */
|
|
+#define NVME_SQE_SIZE_LOG 6
|
|
+#define NVME_CQE_SIZE_LOG 4
|
|
+
|
|
+#endif
|
|
+
|
|
+/* EOF */
|
|
diff --git a/grub-core/disk/nvme.c b/grub-core/disk/nvme.c
|
|
new file mode 100644
|
|
index 000000000..093237c70
|
|
--- /dev/null
|
|
+++ b/grub-core/disk/nvme.c
|
|
@@ -0,0 +1,781 @@
|
|
+// Low level NVMe disk access
|
|
+//
|
|
+// Based on SeaBIOS NVMe driver - Copyright 2017 Amazon.com, Inc. or its affiliates.
|
|
+// Port to GRUB2 done by Mate Kukri
|
|
+//
|
|
+// This file may be distributed under the terms of the GNU LGPLv3 license.
|
|
+
|
|
+#include <grub/disk.h>
|
|
+#include <grub/dl.h>
|
|
+#include <grub/pci.h>
|
|
+#include "nvme-int.h"
|
|
+
|
|
+GRUB_MOD_LICENSE ("GPLv3"); /* LGPLv3 in reality but it is GPLv3 compatible */
|
|
+
|
|
+static grub_uint32_t grub_nvme_ctrlcnt;
|
|
+static grub_uint32_t grub_nvme_nscnt;
|
|
+
|
|
+static struct nvme_namespace *grub_nvme_namespaces;
|
|
+
|
|
+// Page aligned "dma bounce buffer" of size NVME_PAGE_SIZE
|
|
+static void *nvme_dma_buffer;
|
|
+
|
|
+static void *
|
|
+zalloc_page_aligned(grub_uint32_t size)
|
|
+{
|
|
+ void *res = grub_memalign(NVME_PAGE_SIZE, size);
|
|
+ if (res) grub_memset(res, 0, size);
|
|
+ return res;
|
|
+}
|
|
+
|
|
+static void
|
|
+nvme_init_queue_common(struct nvme_ctrl *ctrl, struct nvme_queue *q, grub_uint16_t q_idx,
|
|
+ grub_uint16_t length)
|
|
+{
|
|
+ grub_memset(q, 0, sizeof(*q));
|
|
+ q->dbl = (grub_uint32_t *)((char *)ctrl->reg + 0x1000 + q_idx * ctrl->doorbell_stride);
|
|
+ grub_dprintf("nvme", " q %p q_idx %u dbl %p\n", q, q_idx, q->dbl);
|
|
+ q->mask = length - 1;
|
|
+}
|
|
+
|
|
+static int
|
|
+nvme_init_sq(struct nvme_ctrl *ctrl, struct nvme_sq *sq, grub_uint16_t q_idx, grub_uint16_t length,
|
|
+ struct nvme_cq *cq)
|
|
+{
|
|
+ nvme_init_queue_common(ctrl, &sq->common, q_idx, length);
|
|
+ sq->sqe = zalloc_page_aligned(sizeof(*sq->sqe) * length);
|
|
+
|
|
+ if (!sq->sqe) {
|
|
+ return -1;
|
|
+ }
|
|
+
|
|
+ grub_dprintf("nvme", "sq %p q_idx %u sqe %p\n", sq, q_idx, sq->sqe);
|
|
+ sq->cq = cq;
|
|
+ sq->head = 0;
|
|
+ sq->tail = 0;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int
|
|
+nvme_init_cq(struct nvme_ctrl *ctrl, struct nvme_cq *cq, grub_uint16_t q_idx, grub_uint16_t length)
|
|
+{
|
|
+ nvme_init_queue_common(ctrl, &cq->common, q_idx, length);
|
|
+ cq->cqe = zalloc_page_aligned(sizeof(*cq->cqe) * length);
|
|
+ if (!cq->cqe) {
|
|
+ return -1;
|
|
+ }
|
|
+
|
|
+ cq->head = 0;
|
|
+
|
|
+ /* All CQE phase bits are initialized to zero. This means initially we wait
|
|
+ for the host controller to set these to 1. */
|
|
+ cq->phase = 1;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int
|
|
+nvme_poll_cq(struct nvme_cq *cq)
|
|
+{
|
|
+ grub_uint32_t dw3 = *(volatile grub_uint32_t *) &cq->cqe[cq->head].dword[3];
|
|
+ return (!!(dw3 & NVME_CQE_DW3_P) == cq->phase);
|
|
+}
|
|
+
|
|
+static int
|
|
+nvme_is_cqe_success(struct nvme_cqe const *cqe)
|
|
+{
|
|
+ return ((cqe->status >> 1) & 0xFF) == 0;
|
|
+}
|
|
+
|
|
+static struct nvme_cqe
|
|
+nvme_error_cqe(void)
|
|
+{
|
|
+ struct nvme_cqe r;
|
|
+
|
|
+ /* 0xFF is a vendor specific status code != success. Should be okay for
|
|
+ indicating failure. */
|
|
+ grub_memset(&r, 0xFF, sizeof(r));
|
|
+ return r;
|
|
+}
|
|
+
|
|
+static struct nvme_cqe
|
|
+nvme_consume_cqe(struct nvme_sq *sq)
|
|
+{
|
|
+ struct nvme_cq *cq = sq->cq;
|
|
+
|
|
+ if (!nvme_poll_cq(cq)) {
|
|
+ /* Cannot consume a completion queue entry, if there is none ready. */
|
|
+ return nvme_error_cqe();
|
|
+ }
|
|
+
|
|
+ struct nvme_cqe *cqe = &cq->cqe[cq->head];
|
|
+ grub_uint16_t cq_next_head = (cq->head + 1) & cq->common.mask;
|
|
+ grub_dprintf("nvme", "cq %p head %u -> %u\n", cq, cq->head, cq_next_head);
|
|
+ if (cq_next_head < cq->head) {
|
|
+ grub_dprintf("nvme", "cq %p wrap\n", cq);
|
|
+ cq->phase = ~cq->phase;
|
|
+ }
|
|
+ cq->head = cq_next_head;
|
|
+
|
|
+ /* Update the submission queue head. */
|
|
+ if (cqe->sq_head != sq->head) {
|
|
+ sq->head = cqe->sq_head;
|
|
+ grub_dprintf("nvme", "sq %p advanced to %u\n", sq, cqe->sq_head);
|
|
+ }
|
|
+
|
|
+ /* Tell the controller that we consumed the completion. */
|
|
+ *(volatile grub_uint32_t *) cq->common.dbl = cq->head;
|
|
+
|
|
+ return *cqe;
|
|
+}
|
|
+
|
|
+static struct nvme_cqe
|
|
+nvme_wait(struct nvme_sq *sq)
|
|
+{
|
|
+ // static const unsigned nvme_timeout = 5000 /* ms */;
|
|
+ // grub_uint32_t to = timer_calc(nvme_timeout);
|
|
+ while (!nvme_poll_cq(sq->cq)) {
|
|
+ /* FIXME
|
|
+ yield();
|
|
+
|
|
+ if (timer_check(to)) {
|
|
+ warn_timeout();
|
|
+ return nvme_error_cqe();
|
|
+ }*/
|
|
+ }
|
|
+
|
|
+ return nvme_consume_cqe(sq);
|
|
+}
|
|
+
|
|
+/* Returns the next submission queue entry (or NULL if the queue is full). It
|
|
+ also fills out Command Dword 0 and clears the rest. */
|
|
+static struct nvme_sqe *
|
|
+nvme_get_next_sqe(struct nvme_sq *sq, grub_uint8_t opc, void *metadata, void *data, void *data2)
|
|
+{
|
|
+ if (((sq->head + 1) & sq->common.mask) == sq->tail) {
|
|
+ grub_dprintf("nvme", "submission queue is full\n");
|
|
+ return NULL;
|
|
+ }
|
|
+
|
|
+ struct nvme_sqe *sqe = &sq->sqe[sq->tail];
|
|
+ grub_dprintf("nvme", "sq %p next_sqe %u\n", sq, sq->tail);
|
|
+
|
|
+ grub_memset(sqe, 0, sizeof(*sqe));
|
|
+ sqe->cdw0 = opc | (sq->tail << 16 /* CID */);
|
|
+ sqe->mptr = (grub_uint32_t)metadata;
|
|
+ sqe->dptr_prp1 = (grub_uint32_t)data;
|
|
+ sqe->dptr_prp2 = (grub_uint32_t)data2;
|
|
+
|
|
+ return sqe;
|
|
+}
|
|
+
|
|
+/* Call this after you've filled out an sqe that you've got from nvme_get_next_sqe. */
|
|
+static void
|
|
+nvme_commit_sqe(struct nvme_sq *sq)
|
|
+{
|
|
+ grub_dprintf("nvme", "sq %p commit_sqe %u\n", sq, sq->tail);
|
|
+ sq->tail = (sq->tail + 1) & sq->common.mask;
|
|
+ *(volatile grub_uint32_t *) sq->common.dbl = sq->tail;
|
|
+}
|
|
+
|
|
+/* Perform an identify command on the admin queue and return the resulting
|
|
+ buffer. This may be a NULL pointer, if something failed. This function
|
|
+ cannot be used after initialization, because it uses buffers in tmp zone. */
|
|
+static union nvme_identify *
|
|
+nvme_admin_identify(struct nvme_ctrl *ctrl, grub_uint8_t cns, grub_uint32_t nsid)
|
|
+{
|
|
+ union nvme_identify *identify_buf = zalloc_page_aligned(4096);
|
|
+ if (!identify_buf)
|
|
+ return NULL;
|
|
+
|
|
+ struct nvme_sqe *cmd_identify;
|
|
+ cmd_identify = nvme_get_next_sqe(&ctrl->admin_sq,
|
|
+ NVME_SQE_OPC_ADMIN_IDENTIFY, NULL,
|
|
+ identify_buf, NULL);
|
|
+ if (!cmd_identify)
|
|
+ goto error;
|
|
+
|
|
+ cmd_identify->nsid = nsid;
|
|
+ cmd_identify->dword[10] = cns;
|
|
+
|
|
+ nvme_commit_sqe(&ctrl->admin_sq);
|
|
+
|
|
+ struct nvme_cqe cqe = nvme_wait(&ctrl->admin_sq);
|
|
+
|
|
+ if (!nvme_is_cqe_success(&cqe)) {
|
|
+ goto error;
|
|
+ }
|
|
+
|
|
+ return identify_buf;
|
|
+ error:
|
|
+ grub_free(identify_buf);
|
|
+ return NULL;
|
|
+}
|
|
+
|
|
+static struct nvme_identify_ctrl *
|
|
+nvme_admin_identify_ctrl(struct nvme_ctrl *ctrl)
|
|
+{
|
|
+ return &nvme_admin_identify(ctrl, NVME_ADMIN_IDENTIFY_CNS_ID_CTRL, 0)->ctrl;
|
|
+}
|
|
+
|
|
+static struct nvme_identify_ns *
|
|
+nvme_admin_identify_ns(struct nvme_ctrl *ctrl, grub_uint32_t ns_id)
|
|
+{
|
|
+ return &nvme_admin_identify(ctrl, NVME_ADMIN_IDENTIFY_CNS_ID_NS,
|
|
+ ns_id)->ns;
|
|
+}
|
|
+
|
|
+static void
|
|
+nvme_probe_ns(struct nvme_ctrl *ctrl, grub_uint32_t ns_idx, grub_uint8_t mdts)
|
|
+{
|
|
+ grub_uint32_t ns_id = ns_idx + 1;
|
|
+
|
|
+ struct nvme_identify_ns *id = nvme_admin_identify_ns(ctrl, ns_id);
|
|
+ if (!id) {
|
|
+ grub_dprintf("nvme", "NVMe couldn't identify namespace %u.\n", ns_id);
|
|
+ goto free_buffer;
|
|
+ }
|
|
+
|
|
+ grub_uint8_t current_lba_format = id->flbas & 0xF;
|
|
+ if (current_lba_format > id->nlbaf) {
|
|
+ grub_dprintf("nvme", "NVMe NS %u: current LBA format %u is beyond what the "
|
|
+ " namespace supports (%u)?\n",
|
|
+ ns_id, current_lba_format, id->nlbaf + 1);
|
|
+ goto free_buffer;
|
|
+ }
|
|
+
|
|
+ if (!id->nsze) {
|
|
+ grub_dprintf("nvme", "NVMe NS %u is inactive.\n", ns_id);
|
|
+ goto free_buffer;
|
|
+ }
|
|
+
|
|
+ if (!nvme_dma_buffer) {
|
|
+ nvme_dma_buffer = zalloc_page_aligned(NVME_PAGE_SIZE);
|
|
+ if (!nvme_dma_buffer) {
|
|
+ goto free_buffer;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ struct nvme_namespace *ns = grub_malloc(sizeof(*ns));
|
|
+ if (!ns) {
|
|
+ goto free_buffer;
|
|
+ }
|
|
+ grub_memset(ns, 0, sizeof(*ns));
|
|
+ ns->ctrl = ctrl;
|
|
+ ns->ns_id = ns_id;
|
|
+ ns->lba_count = id->nsze;
|
|
+
|
|
+ struct nvme_lba_format *fmt = &id->lbaf[current_lba_format];
|
|
+
|
|
+ ns->block_size = 1U << fmt->lbads;
|
|
+ ns->metadata_size = fmt->ms;
|
|
+
|
|
+ if (ns->block_size > NVME_PAGE_SIZE) {
|
|
+ /* If we see devices that trigger this path, we need to increase our
|
|
+ buffer size. */
|
|
+ grub_free(ns);
|
|
+ goto free_buffer;
|
|
+ }
|
|
+
|
|
+ if (mdts) {
|
|
+ ns->max_req_size = ((1U << mdts) * NVME_PAGE_SIZE) / ns->block_size;
|
|
+ grub_dprintf("nvme", "NVME NS %u max request size: %d sectors\n",
|
|
+ ns_id, ns->max_req_size);
|
|
+ } else {
|
|
+ ns->max_req_size = -1U;
|
|
+ }
|
|
+
|
|
+ ns->devname = grub_xasprintf("nvme%un%u", ctrl->ctrlnum, ns_id);
|
|
+ ns->nsnum = grub_nvme_nscnt++;
|
|
+
|
|
+ grub_list_push (GRUB_AS_LIST_P (&grub_nvme_namespaces), GRUB_AS_LIST (ns));
|
|
+
|
|
+free_buffer:
|
|
+ grub_free(id);
|
|
+}
|
|
+
|
|
+
|
|
+/* Release memory allocated for a completion queue */
|
|
+static void
|
|
+nvme_destroy_cq(struct nvme_cq *cq)
|
|
+{
|
|
+ grub_free(cq->cqe);
|
|
+ cq->cqe = NULL;
|
|
+}
|
|
+
|
|
+/* Release memory allocated for a submission queue */
|
|
+static void
|
|
+nvme_destroy_sq(struct nvme_sq *sq)
|
|
+{
|
|
+ grub_free(sq->sqe);
|
|
+ sq->sqe = NULL;
|
|
+}
|
|
+
|
|
+/* Returns 0 on success. */
|
|
+static int
|
|
+nvme_create_io_cq(struct nvme_ctrl *ctrl, struct nvme_cq *cq, grub_uint16_t q_idx)
|
|
+{
|
|
+ int rc;
|
|
+ struct nvme_sqe *cmd_create_cq;
|
|
+ grub_uint32_t length = 1 + (ctrl->reg->cap & 0xffff);
|
|
+ if (length > NVME_PAGE_SIZE / sizeof(struct nvme_cqe))
|
|
+ length = NVME_PAGE_SIZE / sizeof(struct nvme_cqe);
|
|
+
|
|
+ rc = nvme_init_cq(ctrl, cq, q_idx, length);
|
|
+ if (rc) {
|
|
+ goto err;
|
|
+ }
|
|
+
|
|
+ cmd_create_cq = nvme_get_next_sqe(&ctrl->admin_sq,
|
|
+ NVME_SQE_OPC_ADMIN_CREATE_IO_CQ, NULL,
|
|
+ cq->cqe, NULL);
|
|
+ if (!cmd_create_cq) {
|
|
+ goto err_destroy_cq;
|
|
+ }
|
|
+
|
|
+ cmd_create_cq->dword[10] = (cq->common.mask << 16) | (q_idx >> 1);
|
|
+ cmd_create_cq->dword[11] = 1 /* physically contiguous */;
|
|
+
|
|
+ nvme_commit_sqe(&ctrl->admin_sq);
|
|
+
|
|
+ struct nvme_cqe cqe = nvme_wait(&ctrl->admin_sq);
|
|
+
|
|
+ if (!nvme_is_cqe_success(&cqe)) {
|
|
+ grub_dprintf("nvme", "create io cq failed: %08x %08x %08x %08x\n",
|
|
+ cqe.dword[0], cqe.dword[1], cqe.dword[2], cqe.dword[3]);
|
|
+
|
|
+ goto err_destroy_cq;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+
|
|
+err_destroy_cq:
|
|
+ nvme_destroy_cq(cq);
|
|
+err:
|
|
+ return -1;
|
|
+}
|
|
+
|
|
+/* Returns 0 on success. */
|
|
+static int
|
|
+nvme_create_io_sq(struct nvme_ctrl *ctrl, struct nvme_sq *sq, grub_uint16_t q_idx, struct nvme_cq *cq)
|
|
+{
|
|
+ int rc;
|
|
+ struct nvme_sqe *cmd_create_sq;
|
|
+ grub_uint32_t length = 1 + (ctrl->reg->cap & 0xffff);
|
|
+ if (length > NVME_PAGE_SIZE / sizeof(struct nvme_cqe))
|
|
+ length = NVME_PAGE_SIZE / sizeof(struct nvme_cqe);
|
|
+
|
|
+ rc = nvme_init_sq(ctrl, sq, q_idx, length, cq);
|
|
+ if (rc) {
|
|
+ goto err;
|
|
+ }
|
|
+
|
|
+ cmd_create_sq = nvme_get_next_sqe(&ctrl->admin_sq,
|
|
+ NVME_SQE_OPC_ADMIN_CREATE_IO_SQ, NULL,
|
|
+ sq->sqe, NULL);
|
|
+ if (!cmd_create_sq) {
|
|
+ goto err_destroy_sq;
|
|
+ }
|
|
+
|
|
+ cmd_create_sq->dword[10] = (sq->common.mask << 16) | (q_idx >> 1);
|
|
+ cmd_create_sq->dword[11] = (q_idx >> 1) << 16 | 1 /* contiguous */;
|
|
+ grub_dprintf("nvme", "sq %p create dword10 %08x dword11 %08x\n", sq,
|
|
+ cmd_create_sq->dword[10], cmd_create_sq->dword[11]);
|
|
+
|
|
+ nvme_commit_sqe(&ctrl->admin_sq);
|
|
+
|
|
+ struct nvme_cqe cqe = nvme_wait(&ctrl->admin_sq);
|
|
+
|
|
+ if (!nvme_is_cqe_success(&cqe)) {
|
|
+ grub_dprintf("nvme", "create io sq failed: %08x %08x %08x %08x\n",
|
|
+ cqe.dword[0], cqe.dword[1], cqe.dword[2], cqe.dword[3]);
|
|
+ goto err_destroy_sq;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+
|
|
+err_destroy_sq:
|
|
+ nvme_destroy_sq(sq);
|
|
+err:
|
|
+ return -1;
|
|
+}
|
|
+
|
|
+/* Reads count sectors into buf. The buffer cannot cross page boundaries. */
|
|
+static int
|
|
+nvme_io_xfer(struct nvme_namespace *ns, grub_uint64_t lba, void *prp1, void *prp2,
|
|
+ grub_uint16_t count, int write)
|
|
+{
|
|
+ if (((grub_uint32_t)prp1 & 0x3) || ((grub_uint32_t)prp2 & 0x3)) {
|
|
+ /* Buffer is misaligned */
|
|
+ return -1;
|
|
+ }
|
|
+
|
|
+ struct nvme_sqe *io_read = nvme_get_next_sqe(&ns->ctrl->io_sq,
|
|
+ write ? NVME_SQE_OPC_IO_WRITE
|
|
+ : NVME_SQE_OPC_IO_READ,
|
|
+ NULL, prp1, prp2);
|
|
+ io_read->nsid = ns->ns_id;
|
|
+ io_read->dword[10] = (grub_uint32_t)lba;
|
|
+ io_read->dword[11] = (grub_uint32_t)(lba >> 32);
|
|
+ io_read->dword[12] = (1U << 31 /* limited retry */) | (count - 1);
|
|
+
|
|
+ nvme_commit_sqe(&ns->ctrl->io_sq);
|
|
+
|
|
+ struct nvme_cqe cqe = nvme_wait(&ns->ctrl->io_sq);
|
|
+
|
|
+ if (!nvme_is_cqe_success(&cqe)) {
|
|
+ grub_dprintf("nvme", "read io: %08x %08x %08x %08x\n",
|
|
+ cqe.dword[0], cqe.dword[1], cqe.dword[2], cqe.dword[3]);
|
|
+
|
|
+ return -1;
|
|
+ }
|
|
+
|
|
+ grub_dprintf("nvme", "ns %u %s lba %llu+%u\n", ns->ns_id, write ? "write" : "read",
|
|
+ lba, count);
|
|
+ return count;
|
|
+}
|
|
+
|
|
+// Transfer up to one page of data using the internal dma bounce buffer
|
|
+static int
|
|
+nvme_bounce_xfer(struct nvme_namespace *ns, grub_uint64_t lba, void *buf, grub_uint16_t count,
|
|
+ int write)
|
|
+{
|
|
+ grub_uint16_t const max_blocks = NVME_PAGE_SIZE / ns->block_size;
|
|
+ grub_uint16_t blocks = count < max_blocks ? count : max_blocks;
|
|
+
|
|
+ if (write)
|
|
+ grub_memcpy(nvme_dma_buffer, buf, blocks * ns->block_size);
|
|
+
|
|
+ int res = nvme_io_xfer(ns, lba, nvme_dma_buffer, NULL, blocks, write);
|
|
+
|
|
+ if (!write && res >= 0)
|
|
+ grub_memcpy(buf, nvme_dma_buffer, res * ns->block_size);
|
|
+
|
|
+ return res;
|
|
+}
|
|
+
|
|
+#define NVME_MAX_PRPL_ENTRIES 15 /* Allows requests up to 64kb */
|
|
+
|
|
+// Transfer data using page list (if applicable)
|
|
+static int
|
|
+nvme_prpl_xfer(struct nvme_namespace *ns, grub_uint64_t lba, void *buf, grub_uint16_t count,
|
|
+ int write)
|
|
+{
|
|
+ grub_uint32_t base = (long)buf;
|
|
+ grub_int32_t size;
|
|
+
|
|
+ if (count > ns->max_req_size)
|
|
+ count = ns->max_req_size;
|
|
+
|
|
+ size = count * ns->block_size;
|
|
+ /* Special case for transfers that fit into PRP1, but are unaligned */
|
|
+ if (((size + (base & ~NVME_PAGE_MASK)) <= NVME_PAGE_SIZE))
|
|
+ goto single;
|
|
+
|
|
+ /* Every request has to be page aligned */
|
|
+ if (base & ~NVME_PAGE_MASK)
|
|
+ goto bounce;
|
|
+
|
|
+ /* Make sure a full block fits into the last chunk */
|
|
+ if (size & (ns->block_size - 1ULL))
|
|
+ goto bounce;
|
|
+
|
|
+ /* Build PRP list if we need to describe more than 2 pages */
|
|
+ if ((ns->block_size * count) > (NVME_PAGE_SIZE * 2)) {
|
|
+ grub_uint32_t prpl_len = 0;
|
|
+ grub_uint64_t *prpl = nvme_dma_buffer;
|
|
+ int first_page = 1;
|
|
+ for (; size > 0; base += NVME_PAGE_SIZE, size -= NVME_PAGE_SIZE) {
|
|
+ if (first_page) {
|
|
+ /* First page is special */
|
|
+ first_page = 0;
|
|
+ continue;
|
|
+ }
|
|
+ if (prpl_len >= NVME_MAX_PRPL_ENTRIES)
|
|
+ goto bounce;
|
|
+ prpl[prpl_len++] = base;
|
|
+ }
|
|
+ return nvme_io_xfer(ns, lba, buf, prpl, count, write);
|
|
+ }
|
|
+
|
|
+ /* Directly embed the 2nd page if we only need 2 pages */
|
|
+ if ((ns->block_size * count) > NVME_PAGE_SIZE)
|
|
+ return nvme_io_xfer(ns, lba, buf, (char *) buf + NVME_PAGE_SIZE, count, write);
|
|
+
|
|
+single:
|
|
+ /* One page is enough, don't expose anything else */
|
|
+ return nvme_io_xfer(ns, lba, buf, NULL, count, write);
|
|
+
|
|
+bounce:
|
|
+ /* Use bounce buffer to make transfer */
|
|
+ return nvme_bounce_xfer(ns, lba, buf, count, write);
|
|
+}
|
|
+
|
|
+static int
|
|
+nvme_create_io_queues(struct nvme_ctrl *ctrl)
|
|
+{
|
|
+ if (nvme_create_io_cq(ctrl, &ctrl->io_cq, 3))
|
|
+ goto err;
|
|
+
|
|
+ if (nvme_create_io_sq(ctrl, &ctrl->io_sq, 2, &ctrl->io_cq))
|
|
+ goto err_free_cq;
|
|
+
|
|
+ return 0;
|
|
+
|
|
+ err_free_cq:
|
|
+ nvme_destroy_cq(&ctrl->io_cq);
|
|
+ err:
|
|
+ return -1;
|
|
+}
|
|
+
|
|
+/* Waits for CSTS.RDY to match rdy. Returns 0 on success. */
|
|
+static int
|
|
+nvme_wait_csts_rdy(struct nvme_ctrl *ctrl, unsigned rdy)
|
|
+{
|
|
+ // grub_uint32_t const max_to = 500 /* ms */ * ((ctrl->reg->cap >> 24) & 0xFFU);
|
|
+ // grub_uint32_t to = timer_calc(max_to);
|
|
+ grub_uint32_t csts;
|
|
+
|
|
+ while (rdy != ((csts = ctrl->reg->csts) & NVME_CSTS_RDY)) {
|
|
+ // FIXME
|
|
+ //yield();
|
|
+
|
|
+ if (csts & NVME_CSTS_FATAL) {
|
|
+ grub_dprintf("nvme", "NVMe fatal error during controller shutdown\n");
|
|
+ return -1;
|
|
+ }
|
|
+
|
|
+ /*
|
|
+ if (timer_check(to)) {
|
|
+ warn_timeout();
|
|
+ return -1;
|
|
+ }*/
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+/* Returns 0 on success. */
|
|
+static int grub_nvme_controller_enable(struct nvme_ctrl *ctrl)
|
|
+{
|
|
+ grub_pci_address_t addr;
|
|
+ int rc;
|
|
+
|
|
+ addr = grub_pci_make_address (ctrl->pci, GRUB_PCI_REG_COMMAND);
|
|
+ grub_pci_write_word (addr, grub_pci_read_word (addr) | GRUB_PCI_COMMAND_BUS_MASTER);
|
|
+
|
|
+ /* Turn the controller off. */
|
|
+ ctrl->reg->cc = 0;
|
|
+ if (nvme_wait_csts_rdy(ctrl, 0)) {
|
|
+ grub_dprintf("nvme", "NVMe fatal error during controller shutdown\n");
|
|
+ return -1;
|
|
+ }
|
|
+
|
|
+ ctrl->doorbell_stride = 4U << ((ctrl->reg->cap >> 32) & 0xF);
|
|
+
|
|
+ rc = nvme_init_cq(ctrl, &ctrl->admin_cq, 1,
|
|
+ NVME_PAGE_SIZE / sizeof(struct nvme_cqe));
|
|
+ if (rc) {
|
|
+ return -1;
|
|
+ }
|
|
+
|
|
+ rc = nvme_init_sq(ctrl, &ctrl->admin_sq, 0,
|
|
+ NVME_PAGE_SIZE / sizeof(struct nvme_sqe), &ctrl->admin_cq);
|
|
+ if (rc) {
|
|
+ goto err_destroy_admin_cq;
|
|
+ }
|
|
+
|
|
+ ctrl->reg->aqa = ctrl->admin_cq.common.mask << 16
|
|
+ | ctrl->admin_sq.common.mask;
|
|
+
|
|
+ ctrl->reg->asq = (grub_uint32_t)ctrl->admin_sq.sqe;
|
|
+ ctrl->reg->acq = (grub_uint32_t)ctrl->admin_cq.cqe;
|
|
+
|
|
+ grub_dprintf("nvme", " admin submission queue: %p\n", ctrl->admin_sq.sqe);
|
|
+ grub_dprintf("nvme", " admin completion queue: %p\n", ctrl->admin_cq.cqe);
|
|
+
|
|
+ ctrl->reg->cc = NVME_CC_EN | (NVME_CQE_SIZE_LOG << 20)
|
|
+ | (NVME_SQE_SIZE_LOG << 16 /* IOSQES */);
|
|
+
|
|
+ if (nvme_wait_csts_rdy(ctrl, 1)) {
|
|
+ grub_dprintf("nvme", "NVMe fatal error while enabling controller\n");
|
|
+ goto err_destroy_admin_sq;
|
|
+ }
|
|
+
|
|
+ /* The admin queue is set up and the controller is ready. Let's figure out
|
|
+ what namespaces we have. */
|
|
+
|
|
+ struct nvme_identify_ctrl *identify = nvme_admin_identify_ctrl(ctrl);
|
|
+
|
|
+ if (!identify) {
|
|
+ grub_dprintf("nvme", "NVMe couldn't identify controller.\n");
|
|
+ goto err_destroy_admin_sq;
|
|
+ }
|
|
+
|
|
+ grub_dprintf("nvme", "NVMe has %u namespace%s.\n",
|
|
+ identify->nn, (identify->nn == 1) ? "" : "s");
|
|
+
|
|
+ ctrl->ns_count = identify->nn;
|
|
+ grub_uint8_t mdts = identify->mdts;
|
|
+ grub_free(identify);
|
|
+
|
|
+ if ((ctrl->ns_count == 0) || nvme_create_io_queues(ctrl)) {
|
|
+ /* No point to continue, if the controller says it doesn't have
|
|
+ namespaces or we couldn't create I/O queues. */
|
|
+ goto err_destroy_admin_sq;
|
|
+ }
|
|
+
|
|
+ /* Give the controller a global number */
|
|
+ ctrl->ctrlnum = grub_nvme_ctrlcnt++;
|
|
+
|
|
+ /* Populate namespace IDs */
|
|
+ for (grub_uint32_t ns_idx = 0; ns_idx < ctrl->ns_count; ns_idx++) {
|
|
+ nvme_probe_ns(ctrl, ns_idx, mdts);
|
|
+ }
|
|
+
|
|
+ grub_dprintf("nvme", "NVMe initialization complete!\n");
|
|
+ return 0;
|
|
+
|
|
+ err_destroy_admin_sq:
|
|
+ nvme_destroy_sq(&ctrl->admin_sq);
|
|
+ err_destroy_admin_cq:
|
|
+ nvme_destroy_cq(&ctrl->admin_cq);
|
|
+ return -1;
|
|
+}
|
|
+
|
|
+static int grub_nvme_pci_probe(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, bar, version;
|
|
+ struct nvme_reg volatile *reg;
|
|
+
|
|
+ class = grub_pci_read (grub_pci_make_address (dev, GRUB_PCI_REG_CLASS));
|
|
+ if (class >> 16 != 0x0108)
|
|
+ return 0;
|
|
+ if ((class >> 8 & 0xff) != 2) { /* as of NVM 1.0e */
|
|
+ grub_dprintf("nvme", "Found incompatble NVMe: prog-if=%02x\n", class >> 8 & 0xff);
|
|
+ return 0;
|
|
+ }
|
|
+
|
|
+ bar = grub_pci_read (grub_pci_make_address (dev, GRUB_PCI_REG_ADDRESS_REG0));
|
|
+ reg = grub_pci_device_map_range (dev, bar & GRUB_PCI_ADDR_MEM_MASK, sizeof (*reg));
|
|
+
|
|
+ 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);
|
|
+
|
|
+ version = reg->vs;
|
|
+ grub_dprintf("nvme", "Found NVMe controller with version %u.%u.%u.\n", version >> 16, (version >> 8) & 0xFF, version & 0xFF);
|
|
+ grub_dprintf("nvme", " Capabilities %016llx\n", reg->cap);
|
|
+
|
|
+ if (~reg->cap & NVME_CAP_CSS_NVME) {
|
|
+ grub_dprintf("nvme", "Controller doesn't speak NVMe command set. Skipping.\n");
|
|
+ goto err;
|
|
+ }
|
|
+
|
|
+ struct nvme_ctrl *ctrl = grub_malloc(sizeof(*ctrl));
|
|
+ if (!ctrl)
|
|
+ goto err;
|
|
+
|
|
+ grub_memset(ctrl, 0, sizeof(*ctrl));
|
|
+
|
|
+ ctrl->reg = reg;
|
|
+ ctrl->pci = dev;
|
|
+
|
|
+ if (grub_nvme_controller_enable(ctrl))
|
|
+ goto err_free_ctrl;
|
|
+
|
|
+ return 0;
|
|
+
|
|
+ err_free_ctrl:
|
|
+ grub_free(ctrl);
|
|
+ err:
|
|
+ grub_dprintf("nvme", "Failed to enable NVMe controller.\n");
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int
|
|
+grub_nvme_iterate (grub_disk_dev_iterate_hook_t hook, void *hook_data, grub_disk_pull_t pull)
|
|
+{
|
|
+ struct nvme_namespace *ns;
|
|
+
|
|
+ if (pull != GRUB_DISK_PULL_NONE)
|
|
+ return 0;
|
|
+
|
|
+ FOR_LIST_ELEMENTS(ns, grub_nvme_namespaces)
|
|
+ if (hook (ns->devname, hook_data))
|
|
+ return 1;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static grub_err_t
|
|
+grub_nvme_open (const char *name __attribute ((unused)), grub_disk_t disk __attribute ((unused)))
|
|
+{
|
|
+ struct nvme_namespace *ns;
|
|
+
|
|
+ FOR_LIST_ELEMENTS(ns, grub_nvme_namespaces)
|
|
+ if (grub_strcmp (ns->devname, name) == 0)
|
|
+ break;
|
|
+
|
|
+ if (! ns)
|
|
+ return grub_error (GRUB_ERR_UNKNOWN_DEVICE, "can't open device");
|
|
+
|
|
+ disk->total_sectors = ns->lba_count;
|
|
+ disk->max_agglomerate = ns->max_req_size;
|
|
+
|
|
+ disk->id = ns->nsnum; /* global id of the namespace */
|
|
+
|
|
+ disk->data = ns;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static grub_err_t
|
|
+nvme_readwrite(struct nvme_namespace *ns, grub_disk_addr_t sector, grub_size_t num_sectors, char *buf, int write)
|
|
+{
|
|
+ for (int i = 0; i < num_sectors;) {
|
|
+ grub_uint16_t blocks_remaining = num_sectors - i;
|
|
+ char *op_buf = buf + i * ns->block_size;
|
|
+ int blocks = nvme_prpl_xfer(ns, sector + i, op_buf, blocks_remaining, write);
|
|
+ if (blocks < 0)
|
|
+ return GRUB_ERR_IO;
|
|
+ i += blocks;
|
|
+ }
|
|
+ return GRUB_ERR_NONE;
|
|
+}
|
|
+
|
|
+static grub_err_t
|
|
+grub_nvme_read (grub_disk_t disk, grub_disk_addr_t sector, grub_size_t num_sectors, char *buf)
|
|
+{
|
|
+ return nvme_readwrite((struct nvme_namespace *) disk->data, sector, num_sectors, buf, 0);
|
|
+}
|
|
+
|
|
+static grub_err_t
|
|
+grub_nvme_write (grub_disk_t disk, grub_disk_addr_t sector, grub_size_t num_sectors, const char *buf)
|
|
+{
|
|
+ return nvme_readwrite((struct nvme_namespace *) disk->data, sector, num_sectors, 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_read = grub_nvme_read,
|
|
+ .disk_write = grub_nvme_write,
|
|
+ .next = 0
|
|
+ };
|
|
+
|
|
+GRUB_MOD_INIT(nvme)
|
|
+{
|
|
+ grub_stop_disk_firmware ();
|
|
+ grub_pci_iterate (grub_nvme_pci_probe, NULL);
|
|
+ grub_disk_dev_register (&grub_nvme_dev);
|
|
+}
|
|
+
|
|
+GRUB_MOD_FINI(nvme)
|
|
+{
|
|
+ grub_disk_dev_unregister (&grub_nvme_dev);
|
|
+}
|
|
diff --git a/include/grub/disk.h b/include/grub/disk.h
|
|
index fbf23df7f..186e76f0b 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.47.3
|
|
|