summaryrefslogtreecommitdiff
path: root/config/module/grub/nvme
diff options
context:
space:
mode:
authorLeah Rowe <leah@libreboot.org>2026-09-18 21:04:00 +0100
committerLeah Rowe <leah@libreboot.org>2026-09-20 13:53:33 +0100
commit076c87ccf68f7fe5a3998e8cd8edd2033a2f2ef0 (patch)
tree9aaf04cfa16565227f733b917af3e3c485505544 /config/module/grub/nvme
parent387e6dd771588b7f8426c8d0934e610626730d51 (diff)
xbmk: make all projects multi-tree
we currently distinguish, by virtue of logic, multi- and single-tree projects. this introduces a lot of complexity, so the following patch has removed this distinction. all single-tree projects are now multi tree, but with only one tree. other design changes: config/git removed. rev/tree now handled with mkhelper and target configs. this allows per-tree remote handling, so we could (for example) add the mrchromebox coreboot fork easily, for specific chromebook models. config/project is now config/module/project. this cleans up the main config/ directory. cfgname variable: .config no longer hardcoded when doing configs in projects. it is configurable with this variable. if unset, the previous .cfg is used noconfig variable: set this in target/mkhelper configs, and config/module/project/tree/config/ is unused; instead, make is called without config copying. a "noconfig" placeholder is used. this enables cleaner usage of previously single-tree projects in the new multi-tree-only structure. cfgvars: variables reinitialised in project config are cleaned more reliably. in particular, several variables e.g. autogenargs were not being reset between projects, although the autogenargs variable is currently unused. general cleanup has been conducted across the build system, as part of this redesign. several parts of the build system more over-engineered as a result of the previous design, and the new design enabled a severe reduction in sloccount (75 lines reduced in lbmk). yes, this change *adds features* but there are fewer lines of code than before. this is the best kind of change. way better than the 3-line-diff. yes. rev= and tree= are set more centrally in configs. the mkhelper config can be used to set url/url_bkup, but individual trees can override them. configs have been cleaned up, with several variables now much more heavily centralised. config/git/ was used before to first download the bare repository, and then the real clone was made with patches. this allows git repositories to be cached for future re-builds. with config/git/ gone, the download logic for projects is more centrally implemented. a separate call to ./mk -f is no longer performed, and in fact ./mk -f never needs to be used anymore as a result. ./mk -f now is basically the same thing as -b, just without any action performed. this change will allow rapid expansion of the libreboot project. i want to add hundreds of chromebooks to the project, using mrchromebox, but the previous design did not permit this. this change means that i can easily have unlimited upstreams per project. coreboot will, in some follow-up changes, have the following upstreams: coreboot/default: coreboot.org coreboot/fam15h: 15h.org fork coreboot/chromebook: mrchromebox fork this can now be done freely without massive changes to the build system. under the previous design, i worked on a now-abandoned patch to do N remotes per project, reading repo links from a file. this meant that i'd have coreboot.org first (main and backup), then mrchromebox. this meant that lbmk would failover through to mrchromebox, wasting time for the user. this new design is much cleaner, and will enable the project to be maintained much more easily. Signed-off-by: Leah Rowe <leah@libreboot.org>
Diffstat (limited to 'config/module/grub/nvme')
-rw-r--r--config/module/grub/nvme/config/payload320
-rw-r--r--config/module/grub/nvme/patches/0001-mitigate-grub-s-missing-characters-for-borders-arrow.patch90
-rw-r--r--config/module/grub/nvme/patches/0002-say-the-name-libreboot-in-the-grub-menu.patch25
-rw-r--r--config/module/grub/nvme/patches/0003-at_keyboard-coreboot-force-scancodes2-translate.patch107
-rw-r--r--config/module/grub/nvme/patches/0004-keylayouts-don-t-print-Unknown-key-message.patch38
-rw-r--r--config/module/grub/nvme/patches/0005-don-t-print-missing-prefix-errors-on-the-screen.patch102
-rw-r--r--config/module/grub/nvme/patches/0006-don-t-print-error-if-module-not-found.patch34
-rw-r--r--config/module/grub/nvme/patches/0007-don-t-print-empty-error-messages.patch31
-rw-r--r--config/module/grub/nvme/patches/0008-Add-native-NVMe-driver-based-on-SeaBIOS.patch1074
-rw-r--r--config/module/grub/nvme/patches/0009-kern-coreboot-mmap-Map-to-reserved.patch37
-rw-r--r--config/module/grub/nvme/patches/0010-Revert-configure-Check-linker-for-image-base-support.patch70
-rw-r--r--config/module/grub/nvme/patches/0011-Revert-configure-Print-a-more-helpful-error-if-autoc.patch30
-rw-r--r--config/module/grub/nvme/patches/0012-bootstrap-Don-t-download-po-files.patch91
-rw-r--r--config/module/grub/nvme/target.cfg5
14 files changed, 2054 insertions, 0 deletions
diff --git a/config/module/grub/nvme/config/payload b/config/module/grub/nvme/config/payload
new file mode 100644
index 00000000..4f3de36e
--- /dev/null
+++ b/config/module/grub/nvme/config/payload
@@ -0,0 +1,320 @@
+# SPDX-License-Identifier: GPL-3.0-or-later
+# Copyright (C) 2014-2016,2020-2021,2023-2025 Leah Rowe <leah@libreboot.org>
+# Copyright (C) 2015 Klemens Nanni <contact@autoboot.org>
+
+set prefix=(memdisk)/boot/grub
+
+insmod at_keyboard
+insmod usb_keyboard
+insmod nativedisk
+insmod ehci
+insmod ohci
+insmod uhci
+insmod usb
+insmod usbms
+insmod regexp
+
+terminal_input --append at_keyboard
+terminal_input --append usb_keyboard
+terminal_output --append cbmemc
+
+# User interface overrides wherever "keystatus" is supported
+# Keep SHIFT key pressed before powering on to disable graphics
+if keystatus --shift; then
+ terminal_output --append vga_text
+else
+ gfxpayload=keep
+ terminal_output --append gfxterm
+
+ for dt in cbfsdisk memdisk; do
+ for it in png jpg; do
+ if [ -f (${dt})/background.${it} ]; then
+ insmod ${it}
+ background_image (${dt})/background.${it}
+ fi
+ done
+ done
+fi
+
+# Keep CTRL pressed to enable default serial terminal (COM1 or the like)
+if keystatus --ctrl; then
+ serial
+ terminal_input --append serial
+ terminal_output --append serial
+fi
+
+# Keep ALT pressed to enable spkmodem
+if keystatus --alt; then
+ terminal_output --append spkmodem
+fi
+
+
+set default="0"
+if [ -f (cbfsdisk)/timeout.cfg ]; then
+ source (cbfsdisk)/timeout.cfg
+else
+ set timeout=8
+fi
+set grub_scan_disk="nvme ahci ata"
+if [ -f (cbfsdisk)/scan.cfg ]; then
+ source (cbfsdisk)/scan.cfg
+fi
+
+if [ -f (cbfsdisk)/keymap.gkb ]; then
+ keymap (cbfsdisk)/keymap.gkb
+fi
+
+function really_try_user_config {
+ set root="${1}"
+
+ if [ -f /"${2}"/grub.cfg ]; then
+ unset superusers
+ configfile /"${2}"/grub.cfg
+ fi
+}
+
+function try_user_config {
+ # The @/... entries are for cases where the BTRFS filesystem is being used
+ for dir in grub boot/grub @/grub @/boot/grub grub2 boot/grub2 @/grub2 @/boot/grub2 boot @/boot; do
+ really_try_user_config "${1}" "${dir}"
+ done
+ for dir in ubuntu debian redhat; do
+ really_try_user_config "${1}" "EFI/${dir}"
+ done
+}
+function search_grub {
+ echo -n "Attempting to load grub.cfg from '${1}' devices"
+ for i in 0 1 2 3 4 5 6 7 8; do
+ for part in 1 2 3 4 5 6 7 8 9 10 11 12; do
+ if [ "${1}" != "nvme" ]; then
+ try_user_config "(${1}${i},${part})"
+ else
+ # TODO: do we care about other namesapces
+ try_user_config "(nvme${i}n1,${part})"
+ fi
+ done
+ if [ "${1}" != "nvme" ]; then
+ # raw devices e.g. (ahci0) instead of (ahci0,1)
+ try_user_config "(${1}${i})"
+ else
+ # TODO: do we care about other namesapces
+ try_user_config "(nvme${i}n1)"
+ fi
+ done
+ echo # Insert newline
+}
+
+function try_isolinux_config {
+ set root="${1}"
+ for dir in '' /boot /EFI /@ /@/boot; do
+ if [ -f "${dir}"/isolinux/isolinux.cfg ]; then
+ syslinux_configfile -i "${dir}"/isolinux/isolinux.cfg
+ elif [ -f "${dir}"/syslinux/syslinux.cfg ]; then
+ syslinux_configfile -s "${dir}"/syslinux/syslinux.cfg
+ elif [ -f "${dir}"/syslinux/extlinux.conf ]; then
+ syslinux_configfile -s "${dir}"/syslinux/extlinux.conf
+ elif [ -f "${dir}"/extlinux/extlinux.conf ]; then
+ syslinux_configfile -s "${dir}"/extlinux/extlinux.conf
+ fi
+ done
+}
+function search_isolinux {
+ echo "\nAttempting to parse iso/sys/extlinux config from '${1}' devices"
+ for i in 0 1 2 3 4 5 6 7 8; do
+ for part in 1 2 3 4 5 6 7 8 9 10 11 12; do
+ if [ "${1}" != "nvme" ]; then
+ try_isolinux_config "(${1}${i},${part})"
+ else
+ # TODO: see above
+ try_isolinux_config "(nvme${i}n1,${part})"
+ fi
+ done
+ if [ "${1}" != "nvme" ]; then
+ # raw devices e.g. (usb0) instead of (usb0,1)
+ try_isolinux_config "(${1}${i})"
+ else
+ # TODO: do we care about other namesapces
+ try_isolinux_config "(nvme${i}n1)"
+ fi
+ done
+ echo # Insert newline
+}
+function try_bootcfg {
+ try_user_config "${1}"
+ try_isolinux_config "${1}"
+}
+function search_bootcfg {
+ search_grub "${1}"
+ search_isolinux "${1}"
+}
+menuentry 'Load Operating System (incl. fully encrypted disks) [o]' --hotkey='o' {
+
+ for grub_disk in ${grub_scan_disk}; do
+ search_bootcfg ${grub_disk}
+ done
+
+ # grub device enumeration is very slow, so checks are hardcoded
+
+ raidvol="md/0 md/1 md/2 md/3 md/4 md/5 md/6 md/7 md/8 md/9"
+
+ # in practise, doing multiple redundant checks is perfectly fast
+ # TODO: optimize grub itself, and use */? here for everything
+
+ for vol in ${raidvol} ; do
+ try_bootcfg "${vol}"
+ done
+
+ unset bootdev
+ for grub_disk in ${grub_scan_disk}; do
+ for i in 0 1 2 3 4 5 6 7 8; do
+ for part in 1 2 3 4 5 6 7 8 9 10 11 12; do
+ if [ "${grub_disk}" = "ahci" ]; then
+ bootdev="${bootdev} (ahci${i},${part})"
+ elif [ "${grub_disk}" = "ata" ]; then
+ bootdev="${bootdev} (ata${i},${part})"
+ elif [ "${grub_disk}" = "nvme" ]; then
+ # TODO: do we care about other namesapces
+ bootdev="${bootdev} (nvme${i}n1,${part})"
+ fi
+ done
+ done
+ done
+
+ set pager=0
+ echo -n "Attempting to unlock encrypted volumes"
+ for dev in ${bootdev} ${raidvol}; do
+ if cryptomount "${dev}" ; then break ; fi
+ done
+ set pager=1
+ echo
+
+ search_bootcfg crypto
+
+ lvmvol=""
+
+ # after cryptomount, lvm volumes might be available
+ # using * is slow on some machines, but we use it here,
+ # just once. in so doing, we find every lvm volume
+ for vol in (*); do
+ if regexp ^\\(lvm/ $vol; then
+ lvmvol="${lvmvol} ${vol}"
+ try_bootcfg "${vol}"
+ fi
+ done
+
+ # user might have put luks inside lvm
+ set pager=0
+ echo "Attempting to unlock encrypted LVMs"
+ for vol in ${lvmvol}; do
+ cryptomount "$vol"
+ done
+ set pager=1
+ echo
+
+ search_bootcfg crypto
+
+ true # Prevent pager requiring to accept each line instead of whole screen
+}
+
+menuentry 'Search for GRUB/SYSLINUX/EXTLINUX/ISOLINUX on USB [s]' --hotkey='s' {
+ search_bootcfg usb
+}
+menuentry 'Search for GRUB/SYSLINUX/EXTLINUX/ISOLINUX on AHCI [a]' --hotkey='a' {
+ search_bootcfg ahci
+}
+menuentry 'Search for GRUB/SYSLINUX/EXTLINUX/ISOLINUX on ATA/IDE [d]' --hotkey='d' {
+ search_bootcfg ata
+}
+menuentry 'Search for GRUB/SYSLINUX/EXTLINUX/ISOLINUX on NVMe [e]' --hotkey='e' {
+ search_bootcfg nvme
+}
+if [ -f (cbfsdisk)/grub.cfg ]; then
+menuentry 'Load configuration (grub.cfg) in CBFS [t]' --hotkey='t' {
+ set root='(cbfsdisk)'
+ if [ -f /grub.cfg ]; then
+ configfile /grub.cfg
+ fi
+}
+fi
+if [ -f (cbfsdisk)/grubtest.cfg ]; then
+menuentry 'Load test configuration (grubtest.cfg) inside of CBFS [t]' --hotkey='t' {
+ set root='(cbfsdisk)'
+ if [ -f /grubtest.cfg ]; then
+ configfile /grubtest.cfg
+ fi
+}
+fi
+if [ -f (cbfsdisk)/u-boot ]; then
+menuentry 'U-Boot i386 payload (experimental) [u]' --hotkey='u' {
+ set root='cbfsdisk'
+ chainloader /u-boot
+}
+fi
+if [ -f (cbfsdisk)/seabios.elf ]; then
+if [ -f (cbfsdisk)/img/u-boot ]; then
+menuentry 'Load SeaBIOS (U-Boot UEFI available in the ESC menu) [b]' --hotkey='b' {
+ set root='cbfsdisk'
+ chainloader /seabios.elf
+}
+else
+menuentry 'Load SeaBIOS [b]' --hotkey='b' {
+ set root='cbfsdisk'
+ chainloader /seabios.elf
+}
+fi
+fi
+if [ -f (cbfsdisk)/img/grub2 ]; then
+if [ -f (cbfsdisk)/img/u-boot ]; then
+menuentry 'Return to SeaBIOS (U-Boot UEFI available in the ESC menu) [b]' --hotkey='b' {
+ set root='cbfsdisk'
+ chainloader /fallback/payload
+}
+else
+menuentry 'Return to SeaBIOS [b]' --hotkey='b' {
+ set root='cbfsdisk'
+ chainloader /fallback/payload
+}
+fi
+fi
+menuentry 'Poweroff [p]' --hotkey='p' {
+ halt
+}
+menuentry 'Reboot [r]' --hotkey='r' {
+ reboot
+}
+if [ -f (cbfsdisk)/img/memtest ]; then
+menuentry 'Load MemTest86+ [m]' --hotkey='m' {
+ set root='cbfsdisk'
+ chainloader /img/memtest
+}
+fi
+
+submenu 'Other [z]' --hotkey='z' {
+ menuentry 'Enable default serial terminal [s]' --hotkey='s' {
+ serial
+ terminal_input --append serial
+ terminal_output --append serial
+ }
+
+ menuentry 'Disable default serial terminal' {
+ terminal_input --remove serial
+ terminal_output --remove serial
+ }
+
+ menuentry 'Enable gfxterm' {
+ terminal_output --append gfxterm
+ terminal_output --remove vga_text
+ }
+ menuentry 'Disable gfxterm [g]' --hotkey='g' {
+ terminal_output --remove gfxterm
+ terminal_output --append vga_text
+ }
+
+ menuentry 'Enable spkmodem [a]' --hotkey='a' {
+ terminal_output --append spkmodem
+ }
+
+ menuentry 'Disable spkmodem [z]' --hotkey='z' {
+ terminal_output --remove spkmodem
+ }
+}
diff --git a/config/module/grub/nvme/patches/0001-mitigate-grub-s-missing-characters-for-borders-arrow.patch b/config/module/grub/nvme/patches/0001-mitigate-grub-s-missing-characters-for-borders-arrow.patch
new file mode 100644
index 00000000..41194e98
--- /dev/null
+++ b/config/module/grub/nvme/patches/0001-mitigate-grub-s-missing-characters-for-borders-arrow.patch
@@ -0,0 +1,90 @@
+From 2c953c9160cbed357437891cf1790aeb1c2bd0f5 Mon Sep 17 00:00:00 2001
+From: Leah Rowe <leah@libreboot.org>
+Date: Sun, 31 Oct 2021 03:47:05 +0000
+Subject: [PATCH 01/10] mitigate grub's missing characters for borders/arrow
+ characters
+
+This cleans up the display on the main screen in GRUB.
+
+Just don't draw a border, at all.
+---
+ grub-core/normal/menu_text.c | 49 ++----------------------------------
+ 1 file changed, 2 insertions(+), 47 deletions(-)
+
+diff --git a/grub-core/normal/menu_text.c b/grub-core/normal/menu_text.c
+index 9c383e64a..8ec1dd1e8 100644
+--- a/grub-core/normal/menu_text.c
++++ b/grub-core/normal/menu_text.c
+@@ -108,47 +108,6 @@ grub_print_message_indented (const char *msg, int margin_left, int margin_right,
+ grub_print_message_indented_real (msg, margin_left, margin_right, term, 0);
+ }
+
+-static void
+-draw_border (struct grub_term_output *term, const struct grub_term_screen_geometry *geo)
+-{
+- int i;
+-
+- grub_term_setcolorstate (term, GRUB_TERM_COLOR_NORMAL);
+-
+- grub_term_gotoxy (term, (struct grub_term_coordinate) { geo->first_entry_x - 1,
+- geo->first_entry_y - 1 });
+- grub_putcode (GRUB_UNICODE_CORNER_UL, term);
+- for (i = 0; i < geo->entry_width + 1; i++)
+- grub_putcode (GRUB_UNICODE_HLINE, term);
+- grub_putcode (GRUB_UNICODE_CORNER_UR, term);
+-
+- for (i = 0; i < geo->num_entries; i++)
+- {
+- grub_term_gotoxy (term, (struct grub_term_coordinate) { geo->first_entry_x - 1,
+- geo->first_entry_y + i });
+- grub_putcode (GRUB_UNICODE_VLINE, term);
+- grub_term_gotoxy (term,
+- (struct grub_term_coordinate) { geo->first_entry_x + geo->entry_width + 1,
+- geo->first_entry_y + i });
+- grub_putcode (GRUB_UNICODE_VLINE, term);
+- }
+-
+- grub_term_gotoxy (term,
+- (struct grub_term_coordinate) { geo->first_entry_x - 1,
+- geo->first_entry_y - 1 + geo->num_entries + 1 });
+- grub_putcode (GRUB_UNICODE_CORNER_LL, term);
+- for (i = 0; i < geo->entry_width + 1; i++)
+- grub_putcode (GRUB_UNICODE_HLINE, term);
+- grub_putcode (GRUB_UNICODE_CORNER_LR, term);
+-
+- grub_term_setcolorstate (term, GRUB_TERM_COLOR_NORMAL);
+-
+- grub_term_gotoxy (term,
+- (struct grub_term_coordinate) { geo->first_entry_x - 1,
+- (geo->first_entry_y - 1 + geo->num_entries
+- + GRUB_TERM_MARGIN + 1) });
+-}
+-
+ static int
+ print_message (int nested, int edit, struct grub_term_output *term, int dry_run)
+ {
+@@ -167,10 +126,8 @@ command-line or ESC to discard edits and return to the GRUB menu."),
+ {
+ char *msg_translated;
+
+- msg_translated = grub_xasprintf (_("Use the %C and %C keys to select which "
+- "entry is highlighted."),
+- GRUB_UNICODE_UPARROW,
+- GRUB_UNICODE_DOWNARROW);
++ msg_translated = grub_xasprintf (_("Use the arrow keys to select which "
++ "entry is highlighted."));
+ if (!msg_translated)
+ return 0;
+ ret += grub_print_message_indented_real (msg_translated, STANDARD_MARGIN,
+@@ -413,8 +370,6 @@ grub_menu_init_page (int nested, int edit,
+
+ grub_term_normal_color = grub_color_menu_normal;
+ grub_term_highlight_color = grub_color_menu_highlight;
+- if (geo->border)
+- draw_border (term, geo);
+ grub_term_normal_color = old_color_normal;
+ grub_term_highlight_color = old_color_highlight;
+ geo->timeout_y = geo->first_entry_y + geo->num_entries
+--
+2.47.3
+
diff --git a/config/module/grub/nvme/patches/0002-say-the-name-libreboot-in-the-grub-menu.patch b/config/module/grub/nvme/patches/0002-say-the-name-libreboot-in-the-grub-menu.patch
new file mode 100644
index 00000000..359f31f8
--- /dev/null
+++ b/config/module/grub/nvme/patches/0002-say-the-name-libreboot-in-the-grub-menu.patch
@@ -0,0 +1,25 @@
+From 71623385f90580b2138110cf00ee66b7a05abcf6 Mon Sep 17 00:00:00 2001
+From: Leah Rowe <leah@libreboot.org>
+Date: Sat, 19 Nov 2022 16:30:24 +0000
+Subject: [PATCH 02/10] say the name libreboot, in the grub menu
+
+---
+ grub-core/normal/main.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/grub-core/normal/main.c b/grub-core/normal/main.c
+index de9a3f961..bed502cde 100644
+--- a/grub-core/normal/main.c
++++ b/grub-core/normal/main.c
+@@ -215,7 +215,7 @@ grub_normal_init_page (struct grub_term_output *term,
+
+ grub_term_cls (term);
+
+- msg_formatted = grub_xasprintf (_("GNU GRUB version %s"), PACKAGE_VERSION);
++ msg_formatted = grub_xasprintf (_("Libreboot 26.01 Magnanimous Max (GRUB menu): https://libreboot.org/"));
+ if (!msg_formatted)
+ return;
+
+--
+2.47.3
+
diff --git a/config/module/grub/nvme/patches/0003-at_keyboard-coreboot-force-scancodes2-translate.patch b/config/module/grub/nvme/patches/0003-at_keyboard-coreboot-force-scancodes2-translate.patch
new file mode 100644
index 00000000..ab26c6b2
--- /dev/null
+++ b/config/module/grub/nvme/patches/0003-at_keyboard-coreboot-force-scancodes2-translate.patch
@@ -0,0 +1,107 @@
+From a21c5e997d6247d713717745b5f7bd380b98fce1 Mon Sep 17 00:00:00 2001
+From: Leah Rowe <leah@libreboot.org>
+Date: Mon, 30 Oct 2023 22:19:21 +0000
+Subject: [PATCH 03/10] at_keyboard coreboot: force scancodes2+translate
+
+Scan code set 2 with translation should be assumed in
+every case, as the default starting position.
+
+However, GRUB is trying to detect and use other modes
+such as set 2 without translation, or set 1 without
+translation from set 2; it also detects no-mode and
+assumes mode 1, on really old keyboards.
+
+The current behaviour has been retained, for everything
+except GRUB_MACHINE_COREBOOT; for the latter, scan code
+set 2 with translation is hardcoded, and forced in code.
+
+This is required to make keyboard initialisation work on
+the MEC5035 EC used by the Dell Latitude E6400, when
+running GRUB as a coreboot payload on that laptop. The
+EC reports scancode set 2 with translation when probed,
+but actually only outputs scancode set 1.
+
+Since GRUB is attempting to use it without translation,
+and since the machine reports set 2 with translation,
+but only ever outputs set 1 scancodes, this results in
+wrong keypresses for every key.
+
+This fix fixed that, by forcing set 2 with translation,
+treating it as set 1, but only on coreboot. This is the
+same behaviour used in GNU+Linux systems and SeaBIOS.
+With this change, GRUB keyboard initialisation now works
+just fine on those machines.
+
+This has *also* been tested on other coreboot machines
+running GRUB; several HP EliteBooks, ThinkPads and
+Dell Precision T1650. All seems to work just fine.
+
+Signed-off-by: Leah Rowe <leah@libreboot.org>
+---
+ grub-core/term/at_keyboard.c | 20 ++++++++++++++++++--
+ 1 file changed, 18 insertions(+), 2 deletions(-)
+
+diff --git a/grub-core/term/at_keyboard.c b/grub-core/term/at_keyboard.c
+index f8a129eb7..8207225c2 100644
+--- a/grub-core/term/at_keyboard.c
++++ b/grub-core/term/at_keyboard.c
+@@ -138,6 +138,7 @@ write_mode (int mode)
+ return (i != GRUB_AT_TRIES);
+ }
+
++#if !defined (GRUB_MACHINE_COREBOOT)
+ static int
+ query_mode (void)
+ {
+@@ -161,10 +162,12 @@ query_mode (void)
+ return 3;
+ return 0;
+ }
++#endif
+
+ static void
+ set_scancodes (void)
+ {
++#if !defined (GRUB_MACHINE_COREBOOT)
+ /* You must have visited computer museum. Keyboard without scancode set
+ knowledge. Assume XT. */
+ if (!grub_keyboard_orig_set)
+@@ -173,20 +176,33 @@ set_scancodes (void)
+ ps2_state.current_set = 1;
+ return;
+ }
++#endif
+
+ #if !USE_SCANCODE_SET
+ ps2_state.current_set = 1;
+ return;
+-#else
++#endif
+
++#if defined (GRUB_MACHINE_COREBOOT)
++ /* enable translation */
++ grub_keyboard_controller_write (grub_keyboard_controller_orig
++ & ~KEYBOARD_AT_DISABLE);
++#else
++ /* if not coreboot, disable translation and try mode 2 first, before 1 */
+ grub_keyboard_controller_write (grub_keyboard_controller_orig
+ & ~KEYBOARD_AT_TRANSLATE
+ & ~KEYBOARD_AT_DISABLE);
++#endif
+
+ keyboard_controller_wait_until_ready ();
+ grub_outb (KEYBOARD_COMMAND_ENABLE, KEYBOARD_REG_DATA);
+-
+ write_mode (2);
++
++#if defined (GRUB_MACHINE_COREBOOT)
++ /* mode 2 with translation, so make grub treat as set 1 */
++ ps2_state.current_set = 1;
++#else
++ /* if not coreboot, translation isn't set; test 2 and fall back to 1 */
+ ps2_state.current_set = query_mode ();
+ grub_dprintf ("atkeyb", "returned set %d\n", ps2_state.current_set);
+ if (ps2_state.current_set == 2)
+--
+2.47.3
+
diff --git a/config/module/grub/nvme/patches/0004-keylayouts-don-t-print-Unknown-key-message.patch b/config/module/grub/nvme/patches/0004-keylayouts-don-t-print-Unknown-key-message.patch
new file mode 100644
index 00000000..68dfb2f7
--- /dev/null
+++ b/config/module/grub/nvme/patches/0004-keylayouts-don-t-print-Unknown-key-message.patch
@@ -0,0 +1,38 @@
+From 062379b1cd5a4f99324c625fb371c29eb00ad4a2 Mon Sep 17 00:00:00 2001
+From: Leah Rowe <leah@libreboot.org>
+Date: Tue, 31 Oct 2023 10:33:28 +0000
+Subject: [PATCH 04/10] keylayouts: don't print "Unknown key" message
+
+on keyboards with stuck keys, this results in GRUB just
+spewing it repeatedly, preventing use of GRUB.
+
+in such cases, it's still possible to use the keyboard,
+and we should let the user at least boot.
+
+it often appears when people plug in faulty usb keyboards,
+but can appear for laptop keyboards too; one of my e6400
+has stuck keys.
+
+with this patch, grub should be a bit more reliable in
+terms of user experience, when the keyboard is faulty.
+
+Signed-off-by: Leah Rowe <leah@libreboot.org>
+---
+ grub-core/commands/keylayouts.c | 1 -
+ 1 file changed, 1 deletion(-)
+
+diff --git a/grub-core/commands/keylayouts.c b/grub-core/commands/keylayouts.c
+index aa3ba34f2..445fa0601 100644
+--- a/grub-core/commands/keylayouts.c
++++ b/grub-core/commands/keylayouts.c
+@@ -174,7 +174,6 @@ grub_term_map_key (grub_keyboard_key_t code, int status)
+ key = map_key_core (code, status, &alt_gr_consumed);
+
+ if (key == 0 || key == GRUB_TERM_SHIFT) {
+- grub_printf ("Unknown key 0x%x detected\n", code);
+ return GRUB_TERM_NO_KEY;
+ }
+
+--
+2.47.3
+
diff --git a/config/module/grub/nvme/patches/0005-don-t-print-missing-prefix-errors-on-the-screen.patch b/config/module/grub/nvme/patches/0005-don-t-print-missing-prefix-errors-on-the-screen.patch
new file mode 100644
index 00000000..53dbdd87
--- /dev/null
+++ b/config/module/grub/nvme/patches/0005-don-t-print-missing-prefix-errors-on-the-screen.patch
@@ -0,0 +1,102 @@
+From e77ca67985c1bef8afd93587232da3d298a8dd51 Mon Sep 17 00:00:00 2001
+From: Leah Rowe <leah@libreboot.org>
+Date: Sun, 5 Nov 2023 16:14:58 +0000
+Subject: [PATCH 05/10] don't print missing prefix errors on the screen
+
+we do actually set the prefix. this patch modifies
+grub to still set grub_errno and return accordingly,
+so the behaviour is otherwise identical, but it will
+no longer print a warning message on the screen.
+
+Signed-off-by: Leah Rowe <leah@libreboot.org>
+---
+ grub-core/commands/keylayouts.c | 2 +-
+ grub-core/commands/loadenv.c | 2 +-
+ grub-core/commands/nativedisk.c | 2 +-
+ grub-core/efiemu/main.c | 3 +--
+ grub-core/font/font.c | 2 +-
+ grub-core/kern/dl.c | 2 +-
+ 6 files changed, 6 insertions(+), 7 deletions(-)
+
+diff --git a/grub-core/commands/keylayouts.c b/grub-core/commands/keylayouts.c
+index 445fa0601..00bcf7025 100644
+--- a/grub-core/commands/keylayouts.c
++++ b/grub-core/commands/keylayouts.c
+@@ -211,7 +211,7 @@ grub_cmd_keymap (struct grub_command *cmd __attribute__ ((unused)),
+ {
+ const char *prefix = grub_env_get ("prefix");
+ if (!prefix)
+- return grub_error (GRUB_ERR_BAD_ARGUMENT, N_("variable `%s' isn't set"), "prefix");
++ return (grub_errno = GRUB_ERR_BAD_ARGUMENT);
+ filename = grub_xasprintf ("%s/layouts/%s.gkb", prefix, argv[0]);
+ if (!filename)
+ return grub_errno;
+diff --git a/grub-core/commands/loadenv.c b/grub-core/commands/loadenv.c
+index 166445849..699b39bfa 100644
+--- a/grub-core/commands/loadenv.c
++++ b/grub-core/commands/loadenv.c
+@@ -58,7 +58,7 @@ open_envblk_file (char *filename,
+ prefix = grub_env_get ("prefix");
+ if (! prefix)
+ {
+- grub_error (GRUB_ERR_FILE_NOT_FOUND, N_("variable `%s' isn't set"), "prefix");
++ grub_errno = GRUB_ERR_FILE_NOT_FOUND;
+ return 0;
+ }
+
+diff --git a/grub-core/commands/nativedisk.c b/grub-core/commands/nativedisk.c
+index 580c8d3b0..6806bff9c 100644
+--- a/grub-core/commands/nativedisk.c
++++ b/grub-core/commands/nativedisk.c
+@@ -186,7 +186,7 @@ grub_cmd_nativedisk (grub_command_t cmd __attribute__ ((unused)),
+ prefix = grub_env_get ("prefix");
+
+ if (! prefix)
+- return grub_error (GRUB_ERR_FILE_NOT_FOUND, N_("variable `%s' isn't set"), "prefix");
++ return (grub_errno = GRUB_ERR_FILE_NOT_FOUND);
+
+ if (prefix)
+ path_prefix = (prefix[0] == '(') ? grub_strchr (prefix, ')') : NULL;
+diff --git a/grub-core/efiemu/main.c b/grub-core/efiemu/main.c
+index e7037f4ed..e5d4dbff1 100644
+--- a/grub-core/efiemu/main.c
++++ b/grub-core/efiemu/main.c
+@@ -231,8 +231,7 @@ grub_efiemu_autocore (void)
+ prefix = grub_env_get ("prefix");
+
+ if (! prefix)
+- return grub_error (GRUB_ERR_FILE_NOT_FOUND,
+- N_("variable `%s' isn't set"), "prefix");
++ return (grub_errno = GRUB_ERR_FILE_NOT_FOUND);
+
+ suffix = grub_efiemu_get_default_core_name ();
+
+diff --git a/grub-core/font/font.c b/grub-core/font/font.c
+index 18de52562..2a0fea6c8 100644
+--- a/grub-core/font/font.c
++++ b/grub-core/font/font.c
+@@ -461,7 +461,7 @@ grub_font_load (const char *filename)
+
+ if (!prefix)
+ {
+- grub_error (GRUB_ERR_FILE_NOT_FOUND, N_("variable `%s' isn't set"), "prefix");
++ grub_errno = GRUB_ERR_FILE_NOT_FOUND;
+ goto fail;
+ }
+ file = try_open_from_prefix (prefix, filename);
+diff --git a/grub-core/kern/dl.c b/grub-core/kern/dl.c
+index de8c3aa8d..eac3ea48d 100644
+--- a/grub-core/kern/dl.c
++++ b/grub-core/kern/dl.c
+@@ -880,7 +880,7 @@ grub_dl_load (const char *name)
+ return 0;
+
+ if (! grub_dl_dir) {
+- grub_error (GRUB_ERR_FILE_NOT_FOUND, N_("variable `%s' isn't set"), "prefix");
++ grub_errno = GRUB_ERR_FILE_NOT_FOUND;
+ return 0;
+ }
+
+--
+2.47.3
+
diff --git a/config/module/grub/nvme/patches/0006-don-t-print-error-if-module-not-found.patch b/config/module/grub/nvme/patches/0006-don-t-print-error-if-module-not-found.patch
new file mode 100644
index 00000000..3851c7fe
--- /dev/null
+++ b/config/module/grub/nvme/patches/0006-don-t-print-error-if-module-not-found.patch
@@ -0,0 +1,34 @@
+From 9412b6be27bce2d79f154761387d35f44184c069 Mon Sep 17 00:00:00 2001
+From: Leah Rowe <leah@libreboot.org>
+Date: Sun, 5 Nov 2023 16:36:22 +0000
+Subject: [PATCH 06/10] don't print error if module not found
+
+still set grub_errno accordingly, and otherwise
+behave the same. in libreboot, we remove a lot of
+modules but then rely on loading a grub.cfg
+provided by a distro; in almost all cases that works,
+but also in almost all cases, that will try to load
+a module we don't actually need, but then it prints
+a message. this can annoy some users, so silence it.
+
+Signed-off-by: Leah Rowe <leah@libreboot.org>
+---
+ grub-core/kern/dl.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/grub-core/kern/dl.c b/grub-core/kern/dl.c
+index eac3ea48d..6d67ba54f 100644
+--- a/grub-core/kern/dl.c
++++ b/grub-core/kern/dl.c
+@@ -510,7 +510,7 @@ grub_dl_resolve_name (grub_dl_t mod, Elf_Ehdr *e)
+
+ s = grub_dl_find_section (e, ".modname");
+ if (!s)
+- return grub_error (GRUB_ERR_BAD_MODULE, "no module name found");
++ return (grub_errno = GRUB_ERR_BAD_MODULE);
+
+ mod->name = grub_strdup ((char *) e + s->sh_offset);
+ if (! mod->name)
+--
+2.47.3
+
diff --git a/config/module/grub/nvme/patches/0007-don-t-print-empty-error-messages.patch b/config/module/grub/nvme/patches/0007-don-t-print-empty-error-messages.patch
new file mode 100644
index 00000000..aa42420f
--- /dev/null
+++ b/config/module/grub/nvme/patches/0007-don-t-print-empty-error-messages.patch
@@ -0,0 +1,31 @@
+From 2ad50d482cae148df832ccd68f90b8111684ecde Mon Sep 17 00:00:00 2001
+From: Leah Rowe <leah@libreboot.org>
+Date: Sun, 5 Nov 2023 17:25:20 +0000
+Subject: [PATCH 07/10] don't print empty error messages
+
+this is part two of the quest to kill the prefix
+error message. after i disabled prefix-related
+messages, it still printed "error: ." on screen.
+
+Signed-off-by: Leah Rowe <leah@libreboot.org>
+---
+ grub-core/kern/err.c | 3 ++-
+ 1 file changed, 2 insertions(+), 1 deletion(-)
+
+diff --git a/grub-core/kern/err.c b/grub-core/kern/err.c
+index ba04b57fb..dab62646d 100644
+--- a/grub-core/kern/err.c
++++ b/grub-core/kern/err.c
+@@ -116,7 +116,8 @@ grub_print_error (void)
+ {
+ if (grub_errno != GRUB_ERR_NONE)
+ {
+- grub_err_printf (_("error: %s.\n"), grub_errmsg);
++ if (grub_strlen(grub_errmsg) > 0)
++ grub_err_printf (_("error: %s.\n"), grub_errmsg);
+ grub_err_printed_errors++;
+ }
+ }
+--
+2.47.3
+
diff --git a/config/module/grub/nvme/patches/0008-Add-native-NVMe-driver-based-on-SeaBIOS.patch b/config/module/grub/nvme/patches/0008-Add-native-NVMe-driver-based-on-SeaBIOS.patch
new file mode 100644
index 00000000..e0b40f2f
--- /dev/null
+++ b/config/module/grub/nvme/patches/0008-Add-native-NVMe-driver-based-on-SeaBIOS.patch
@@ -0,0 +1,1074 @@
+From 202e20bef3766480aee18e679b9306387be615dc Mon Sep 17 00:00:00 2001
+From: Mate Kukri <km@mkukri.xyz>
+Date: Mon, 20 May 2024 11:43:35 +0100
+Subject: [PATCH 08/10] 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 0cf155128..176d35364 100644
+--- a/grub-core/Makefile.core.def
++++ b/grub-core/Makefile.core.def
+@@ -2773,3 +2773,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
+
diff --git a/config/module/grub/nvme/patches/0009-kern-coreboot-mmap-Map-to-reserved.patch b/config/module/grub/nvme/patches/0009-kern-coreboot-mmap-Map-to-reserved.patch
new file mode 100644
index 00000000..11b82429
--- /dev/null
+++ b/config/module/grub/nvme/patches/0009-kern-coreboot-mmap-Map-to-reserved.patch
@@ -0,0 +1,37 @@
+From 9ec8534a4d27db37cf4feebd44fd9f8973a89e59 Mon Sep 17 00:00:00 2001
+From: Paul Menzel <pmenzel@molgen.mpg.de>
+Date: Mon, 17 May 2021 10:24:36 +0200
+Subject: [PATCH 09/10] kern/coreboot/mmap: Map to reserved
+
+https://git.savannah.gnu.org/cgit/grub.git/commit/?id=6de9ee86bf9ae50967413e6a73b5dfd13e5ffb50
+
+Signed-off-by: Paul Menzel <pmenzel@molgen.mpg.de>
+---
+ grub-core/kern/coreboot/mmap.c | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+diff --git a/grub-core/kern/coreboot/mmap.c b/grub-core/kern/coreboot/mmap.c
+index caf8f7cef..2fc316e8d 100644
+--- a/grub-core/kern/coreboot/mmap.c
++++ b/grub-core/kern/coreboot/mmap.c
+@@ -59,7 +59,7 @@ iterate_linuxbios_table (grub_linuxbios_table_item_t table_item, void *data)
+ /* Multiboot mmaps match with the coreboot mmap
+ definition. Therefore, we can just pass type
+ through. */
+- mem_region->type,
++ (mem_region->type >= 13) ? 2 : mem_region->type,
+ ctx->hook_data))
+ return 1;
+ if (start < 0xa0000)
+@@ -81,7 +81,7 @@ iterate_linuxbios_table (grub_linuxbios_table_item_t table_item, void *data)
+ /* Multiboot mmaps match with the coreboot mmap
+ definition. Therefore, we can just pass type
+ through. */
+- mem_region->type,
++ (mem_region->type >= 13) ? 2 : mem_region->type,
+ ctx->hook_data))
+ return 1;
+ }
+--
+2.47.3
+
diff --git a/config/module/grub/nvme/patches/0010-Revert-configure-Check-linker-for-image-base-support.patch b/config/module/grub/nvme/patches/0010-Revert-configure-Check-linker-for-image-base-support.patch
new file mode 100644
index 00000000..8e78e8b7
--- /dev/null
+++ b/config/module/grub/nvme/patches/0010-Revert-configure-Check-linker-for-image-base-support.patch
@@ -0,0 +1,70 @@
+From 7b0d621389fc1857c918058f82093f9630ea7730 Mon Sep 17 00:00:00 2001
+From: Leah Rowe <leah@libreboot.org>
+Date: Wed, 24 Dec 2025 01:42:17 +0100
+Subject: [PATCH 10/10] Revert "configure: Check linker for --image-base
+ support"
+
+This reverts commit 1a5417f39a0ccefcdd5440f2a67f84d2d2e26960.
+---
+ acinclude.m4 | 5 -----
+ configure.ac | 14 ++------------
+ 2 files changed, 2 insertions(+), 17 deletions(-)
+
+diff --git a/acinclude.m4 b/acinclude.m4
+index 70c1912f8..fa7840f09 100644
+--- a/acinclude.m4
++++ b/acinclude.m4
+@@ -79,11 +79,6 @@ AC_DEFUN([grub_PROG_OBJCOPY_ABSOLUTE],
+ [AC_MSG_CHECKING([whether ${TARGET_OBJCOPY} works for absolute addresses])
+ AC_CACHE_VAL(grub_cv_prog_objcopy_absolute,
+ [cat > conftest.c <<\EOF
+-asm (
+- ".globl start, _start, __start\n"
+- ".ifdef cmain; .set start = _start = __start = cmain\n.endif\n"
+- ".ifdef _cmain; .set start = _start = __start = _cmain\n.endif\n"
+-);
+ void cmain (void);
+ void
+ cmain (void)
+diff --git a/configure.ac b/configure.ac
+index d8ca1b7c1..041cfbab4 100644
+--- a/configure.ac
++++ b/configure.ac
+@@ -1461,6 +1461,7 @@ elif test x$grub_cv_target_cc_link_format = x-mi386pe || test x$grub_cv_target_c
+ TARGET_IMG_LDSCRIPT='$(top_srcdir)'"/conf/i386-cygwin-img-ld.sc"
+ TARGET_IMG_LDFLAGS="-Wl,-T${TARGET_IMG_LDSCRIPT}"
+ TARGET_IMG_LDFLAGS_AC="-Wl,-T${srcdir}/conf/i386-cygwin-img-ld.sc"
++ TARGET_IMG_BASE_LDOPT="-Wl,-Ttext"
+ TARGET_IMG_CFLAGS=
+ else
+ TARGET_APPLE_LINKER=0
+@@ -1468,6 +1469,7 @@ else
+ TARGET_IMG_LDSCRIPT=
+ TARGET_IMG_LDFLAGS='-Wl,-N'
+ TARGET_IMG_LDFLAGS_AC='-Wl,-N'
++ TARGET_IMG_BASE_LDOPT="-Wl,-Ttext"
+ TARGET_IMG_CFLAGS=
+ fi
+
+@@ -1798,18 +1800,6 @@ grub_PROG_TARGET_CC
+ m4_ifndef([AX_CHECK_LINK_FLAG], [m4_fatal([autoconf-archive is missing. You must install it to generate the configure script.])])
+
+ if test "x$TARGET_APPLE_LINKER" != x1 ; then
+-AX_CHECK_LINK_FLAG([-Wl,--image-base,0x400000],
+- [TARGET_IMG_BASE_LDOPT="-Wl,--image-base"],
+- [TARGET_IMG_BASE_LDOPT="-Wl,-Ttext"],
+- [],
+- [AC_LANG_SOURCE([
+-asm (".globl start; start:");
+-asm (".globl _start; _start:");
+-asm (".globl __start; __start:");
+-void __main (void);
+-void __main (void) {}
+-int main (void);
+- ])])
+ grub_PROG_OBJCOPY_ABSOLUTE
+ fi
+ grub_PROG_LD_BUILD_ID_NONE
+--
+2.47.3
+
diff --git a/config/module/grub/nvme/patches/0011-Revert-configure-Print-a-more-helpful-error-if-autoc.patch b/config/module/grub/nvme/patches/0011-Revert-configure-Print-a-more-helpful-error-if-autoc.patch
new file mode 100644
index 00000000..c0a504ff
--- /dev/null
+++ b/config/module/grub/nvme/patches/0011-Revert-configure-Print-a-more-helpful-error-if-autoc.patch
@@ -0,0 +1,30 @@
+From abf2bf6e2973ccaa994f63ff851ba11cbd45f1eb Mon Sep 17 00:00:00 2001
+From: Leah Rowe <leah@libreboot.org>
+Date: Mon, 19 Jan 2026 15:27:23 +0000
+Subject: [PATCH 1/1] Revert "configure: Print a more helpful error if
+ autoconf-archive is not installed"
+
+This reverts commit ac042f3f58d33ce9cd5ff61750f06da1a1d7b0eb.
+---
+ configure.ac | 5 -----
+ 1 file changed, 5 deletions(-)
+
+diff --git a/configure.ac b/configure.ac
+index 041cfbab4..209c0fb11 100644
+--- a/configure.ac
++++ b/configure.ac
+@@ -1794,11 +1794,6 @@ LIBS=""
+ # Defined in acinclude.m4.
+ grub_ASM_USCORE
+ grub_PROG_TARGET_CC
+-
+-# The error message produced by autoconf if autoconf-archive is not installed is
+-# quite misleading and not very helpful. So, try point people in the right direction.
+-m4_ifndef([AX_CHECK_LINK_FLAG], [m4_fatal([autoconf-archive is missing. You must install it to generate the configure script.])])
+-
+ if test "x$TARGET_APPLE_LINKER" != x1 ; then
+ grub_PROG_OBJCOPY_ABSOLUTE
+ fi
+--
+2.47.3
+
diff --git a/config/module/grub/nvme/patches/0012-bootstrap-Don-t-download-po-files.patch b/config/module/grub/nvme/patches/0012-bootstrap-Don-t-download-po-files.patch
new file mode 100644
index 00000000..cfb66047
--- /dev/null
+++ b/config/module/grub/nvme/patches/0012-bootstrap-Don-t-download-po-files.patch
@@ -0,0 +1,91 @@
+From 5d18c96a22d98d137ea40bfc6aabadce933c2d45 Mon Sep 17 00:00:00 2001
+From: Leah Rowe <leah@libreboot.org>
+Date: Sun, 1 Feb 2026 20:30:55 +0100
+Subject: [PATCH 1/1] bootstrap: Don't download po files
+
+GRUB doesn't verify checksums at all, and it pulls from
+this URL recursively:
+
+https://translationproject.org/latest/grub/
+
+These files can change at any time, and GRUB is just
+downloading them trustingly. Even if the upstream is
+totally benevolent, what if they got hacked?
+
+I downloaded them, hashed them and decided to mirror
+them on my RSYNC mirror. In this way, Libreboot can now
+use them in a deterministic fashion.
+
+Simply adding them to the GRUB source code would mean
+patching GRUB, which would add 8MB to lbmk. I won't do
+it.
+
+Signed-off-by: Leah Rowe <leah@libreboot.org>
+---
+ bootstrap | 31 +++++++++++++------------------
+ 1 file changed, 13 insertions(+), 18 deletions(-)
+
+diff --git a/bootstrap b/bootstrap
+index dc9fb4383..9fc5a5c36 100755
+--- a/bootstrap
++++ b/bootstrap
+@@ -1,5 +1,16 @@
+ #! /bin/sh
+-# DO NOT EDIT! GENERATED AUTOMATICALLY!
++# THIS FILE WAS EDITED BY LIBREBOOT TO REMOVE
++# HACKY GRUB BEHAVIOUR; po files now downloaded
++# by lbmk, via config/submodule/grub/ - so that
++# versioned files are possible, with proper checksum
++# verification, and mirrors are used.
++
++# Yes. This file has been modified. I intend to
++# eventually remove this hacky script. Probably
++# replace the entire GRUB build system.
++
++# Please do fix/edit or (when possible) remove
++# this file. Thank you.
+
+ # Bootstrap this package from checked-out sources.
+
+@@ -145,13 +156,6 @@ bootstrap_post_import_hook() { :; }
+ # Override it via your own definition in bootstrap.conf.
+ bootstrap_epilogue() { :; }
+
+-# The command to download all .po files for a specified domain into a
+-# specified directory. Fill in the first %s with the destination
+-# directory and the second with the domain name.
+-po_download_command_format=\
+-"wget --mirror --level=1 -nd -nv -A.po -P '%s' \
+- https://translationproject.org/latest/%s/"
+-
+ # When extracting the package name from an AC_INIT invocation,
+ # prefer a non-empty tarname (4th argument of AC_INIT if given), else
+ # fall back to the package name (1st argument with munging).
+@@ -909,14 +913,6 @@ autopull()
+
+ # ----------------------------- Get translations. -----------------------------
+
+-download_po_files() {
+- subdir=$1
+- domain=$2
+- echo "$me: getting translations into $subdir for $domain..."
+- cmd=$(printf "$po_download_command_format" "$subdir" "$domain")
+- eval "$cmd"
+-}
+-
+ # Mirror .po files to $po_dir/.reference and copy only the new
+ # or modified ones into $po_dir. Also update $po_dir/LINGUAS.
+ # Note po files that exist locally only are left in $po_dir but will
+@@ -932,8 +928,7 @@ update_po_files() {
+ ref_po_dir="$po_dir/.reference"
+
+ test -d $ref_po_dir || mkdir $ref_po_dir || return
+- download_po_files $ref_po_dir $domain \
+- && ls "$ref_po_dir"/*.po 2>/dev/null |
++ ls "$ref_po_dir"/*.po 2>/dev/null |
+ sed 's|.*/||; s|\.po$||' > "$po_dir/LINGUAS" || return
+
+ for po in x $(ls $ref_po_dir | sed -n 's/\.po$//p'); do
+--
+2.47.3
+
diff --git a/config/module/grub/nvme/target.cfg b/config/module/grub/nvme/target.cfg
new file mode 100644
index 00000000..15102b28
--- /dev/null
+++ b/config/module/grub/nvme/target.cfg
@@ -0,0 +1,5 @@
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+rev="eaa3b8f0f90605a82c6bfda4c5c4b73c58eb81ac"
+
+tree="nvme"