nalika / rpms / grub2

Forked from rpms/grub2 2 years ago
Clone
6fd602
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
6fd602
From: =?UTF-8?q?Renaud=20M=C3=A9trich?= <rmetrich@redhat.com>
6fd602
Date: Tue, 15 Feb 2022 14:05:22 +0100
6fd602
Subject: [PATCH] efi: new 'connectefi' command
6fd602
MIME-Version: 1.0
6fd602
Content-Type: text/plain; charset=UTF-8
6fd602
Content-Transfer-Encoding: 8bit
6fd602
6fd602
When efi.quickboot is enabled on VMWare (which is the default for
6fd602
hardware release 16 and later), it may happen that not all EFI devices
6fd602
are connected. Due to this, browsing the devices in make_devices() just
6fd602
fails to find devices, in particular disks or partitions for a given
6fd602
disk.
6fd602
This typically happens when network booting, then trying to chainload to
6fd602
local disk (this is used in deployment tools such as Red Hat Satellite),
6fd602
which is done through using the following grub.cfg snippet:
6fd602
-------- 8< ---------------- 8< ---------------- 8< --------
6fd602
unset prefix
6fd602
search --file --set=prefix /EFI/redhat/grubx64.efi
6fd602
if [ -n "$prefix" ]; then
6fd602
  chainloader ($prefix)/EFI/redhat/grubx64/efi
6fd602
...
6fd602
-------- 8< ---------------- 8< ---------------- 8< --------
6fd602
6fd602
With efi.quickboot, none of the devices are connected, causing "search"
6fd602
to fail. Sometimes devices are connected but not the partition of the
6fd602
disk matching $prefix, causing partition to not be found by
6fd602
"chainloader".
6fd602
6fd602
This patch introduces a new "connectefi pciroot|scsi" command which
6fd602
recursively connects all EFI devices starting from a given controller
6fd602
type:
6fd602
- if 'pciroot' is specified, recursion is performed for all PCI root
6fd602
  handles
6fd602
- if 'scsi' is specified, recursion is performed for all SCSI I/O
6fd602
  handles (recommended usage to avoid connecting unwanted handles which
6fd602
  may impact Grub performances)
6fd602
6fd602
Typical grub.cfg snippet would then be:
6fd602
-------- 8< ---------------- 8< ---------------- 8< --------
6fd602
connectefi scsi
6fd602
unset prefix
6fd602
search --file --set=prefix /EFI/redhat/grubx64.efi
6fd602
if [ -n "$prefix" ]; then
6fd602
  chainloader ($prefix)/EFI/redhat/grubx64/efi
6fd602
...
6fd602
-------- 8< ---------------- 8< ---------------- 8< --------
6fd602
6fd602
The code is easily extensible to handle other arguments in the future if
6fd602
needed.
6fd602
6fd602
Signed-off-by: Renaud Métrich <rmetrich@redhat.com>
6fd602
Signed-off-by: Robbie Harwood <rharwood@redhat.com>
6fd602
---
6fd602
 grub-core/Makefile.core.def         |   6 ++
6fd602
 grub-core/commands/efi/connectefi.c | 205 ++++++++++++++++++++++++++++++++++++
6fd602
 grub-core/commands/efi/lsefi.c      |   1 +
6fd602
 grub-core/disk/efi/efidisk.c        |  13 +++
6fd602
 grub-core/kern/efi/efi.c            |  13 +++
6fd602
 include/grub/efi/disk.h             |   2 +
6fd602
 include/grub/efi/efi.h              |   5 +
6fd602
 NEWS                                |   2 +-
6fd602
 8 files changed, 246 insertions(+), 1 deletion(-)
6fd602
 create mode 100644 grub-core/commands/efi/connectefi.c
6fd602
6fd602
diff --git a/grub-core/Makefile.core.def b/grub-core/Makefile.core.def
b35c50
index ec1ec5083b..741a033978 100644
6fd602
--- a/grub-core/Makefile.core.def
6fd602
+++ b/grub-core/Makefile.core.def
b35c50
@@ -836,6 +836,12 @@ module = {
6fd602
   enable = efi;
6fd602
 };
6fd602
 
6fd602
+module = {
6fd602
+  name = connectefi;
6fd602
+  common = commands/efi/connectefi.c;
6fd602
+  enable = efi;
6fd602
+};
6fd602
+
6fd602
 module = {
6fd602
   name = blocklist;
6fd602
   common = commands/blocklist.c;
6fd602
diff --git a/grub-core/commands/efi/connectefi.c b/grub-core/commands/efi/connectefi.c
6fd602
new file mode 100644
b35c50
index 0000000000..8ab75bd51b
6fd602
--- /dev/null
6fd602
+++ b/grub-core/commands/efi/connectefi.c
6fd602
@@ -0,0 +1,205 @@
6fd602
+/*
6fd602
+ *  GRUB  --  GRand Unified Bootloader
6fd602
+ *  Copyright (C) 2022  Free Software Foundation, Inc.
6fd602
+ *
6fd602
+ *  GRUB is free software: you can redistribute it and/or modify
6fd602
+ *  it under the terms of the GNU General Public License as published by
6fd602
+ *  the Free Software Foundation, either version 3 of the License, or
6fd602
+ *  (at your option) any later version.
6fd602
+ *
6fd602
+ *  GRUB is distributed in the hope that it will be useful,
6fd602
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
6fd602
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
6fd602
+ *  GNU General Public License for more details.
6fd602
+ *
6fd602
+ *  You should have received a copy of the GNU General Public License
6fd602
+ *  along with GRUB.  If not, see <http://www.gnu.org/licenses/>.
6fd602
+ */
6fd602
+#include <grub/types.h>
6fd602
+#include <grub/mm.h>
6fd602
+#include <grub/misc.h>
6fd602
+#include <grub/efi/api.h>
6fd602
+#include <grub/efi/pci.h>
6fd602
+#include <grub/efi/efi.h>
6fd602
+#include <grub/command.h>
6fd602
+#include <grub/err.h>
6fd602
+#include <grub/i18n.h>
6fd602
+
6fd602
+GRUB_MOD_LICENSE ("GPLv3+");
6fd602
+
6fd602
+typedef struct handle_list
6fd602
+{
6fd602
+  grub_efi_handle_t handle;
6fd602
+  struct handle_list *next;
6fd602
+} handle_list_t;
6fd602
+
6fd602
+static handle_list_t *already_handled = NULL;
6fd602
+
6fd602
+static grub_err_t
6fd602
+add_handle (grub_efi_handle_t handle)
6fd602
+{
6fd602
+  handle_list_t *e;
6fd602
+  e = grub_malloc (sizeof (*e));
6fd602
+  if (! e)
6fd602
+    return grub_errno;
6fd602
+  e->handle = handle;
6fd602
+  e->next = already_handled;
6fd602
+  already_handled = e;
6fd602
+  return GRUB_ERR_NONE;
6fd602
+}
6fd602
+
6fd602
+static int
6fd602
+is_in_list (grub_efi_handle_t handle)
6fd602
+{
6fd602
+  handle_list_t *e;
6fd602
+  for (e = already_handled; e != NULL; e = e->next)
6fd602
+    if (e->handle == handle)
6fd602
+      return 1;
6fd602
+  return 0;
6fd602
+}
6fd602
+
6fd602
+static void
6fd602
+free_handle_list (void)
6fd602
+{
6fd602
+  handle_list_t *e;
6fd602
+  while ((e = already_handled) != NULL)
6fd602
+    {
6fd602
+      already_handled = already_handled->next;
6fd602
+      grub_free (e);
6fd602
+    }
6fd602
+}
6fd602
+
6fd602
+typedef enum searched_item_flag
6fd602
+{
6fd602
+  SEARCHED_ITEM_FLAG_LOOP = 1,
6fd602
+  SEARCHED_ITEM_FLAG_RECURSIVE = 2
6fd602
+} searched_item_flags;
6fd602
+
6fd602
+typedef struct searched_item
6fd602
+{
6fd602
+  grub_efi_guid_t guid;
6fd602
+  const char *name;
6fd602
+  searched_item_flags flags;
6fd602
+} searched_items;
6fd602
+
6fd602
+static grub_err_t
6fd602
+grub_cmd_connectefi (grub_command_t cmd __attribute__ ((unused)),
6fd602
+		     int argc, char **args)
6fd602
+{
6fd602
+  unsigned s;
6fd602
+  searched_items pciroot_items[] =
6fd602
+    {
6fd602
+      { GRUB_EFI_PCI_ROOT_IO_GUID, "PCI root", SEARCHED_ITEM_FLAG_RECURSIVE }
6fd602
+    };
6fd602
+  searched_items scsi_items[] =
6fd602
+    {
6fd602
+      { GRUB_EFI_PCI_ROOT_IO_GUID, "PCI root", 0 },
6fd602
+      { GRUB_EFI_PCI_IO_GUID, "PCI", SEARCHED_ITEM_FLAG_LOOP },
6fd602
+      { GRUB_EFI_SCSI_IO_PROTOCOL_GUID, "SCSI I/O", SEARCHED_ITEM_FLAG_RECURSIVE }
6fd602
+    };
6fd602
+  searched_items *items = NULL;
6fd602
+  unsigned nitems = 0;
6fd602
+  grub_err_t grub_err = GRUB_ERR_NONE;
6fd602
+  unsigned total_connected = 0;
6fd602
+
6fd602
+  if (argc != 1)
6fd602
+    return grub_error (GRUB_ERR_BAD_ARGUMENT, N_("one argument expected"));
6fd602
+
6fd602
+  if (grub_strcmp(args[0], N_("pciroot")) == 0)
6fd602
+    {
6fd602
+      items = pciroot_items;
6fd602
+      nitems = ARRAY_SIZE (pciroot_items);
6fd602
+    }
6fd602
+  else if (grub_strcmp(args[0], N_("scsi")) == 0)
6fd602
+    {
6fd602
+      items = scsi_items;
6fd602
+      nitems = ARRAY_SIZE (scsi_items);
6fd602
+    }
6fd602
+  else
6fd602
+    return grub_error (GRUB_ERR_BAD_ARGUMENT,
6fd602
+		       N_("unexpected argument `%s'"), args[0]);
6fd602
+
6fd602
+  for (s = 0; s < nitems; s++)
6fd602
+    {
6fd602
+      grub_efi_handle_t *handles;
6fd602
+      grub_efi_uintn_t num_handles;
6fd602
+      unsigned i, connected = 0, loop = 0;
6fd602
+
6fd602
+loop:
6fd602
+      loop++;
6fd602
+      grub_dprintf ("efi", "step '%s' loop %d:\n", items[s].name, loop);
6fd602
+
6fd602
+      handles = grub_efi_locate_handle (GRUB_EFI_BY_PROTOCOL,
6fd602
+					&items[s].guid, 0, &num_handles);
6fd602
+
6fd602
+      if (!handles)
6fd602
+	continue;
6fd602
+
6fd602
+      for (i = 0; i < num_handles; i++)
6fd602
+	{
6fd602
+	  grub_efi_handle_t handle = handles[i];
6fd602
+	  grub_efi_status_t status;
6fd602
+	  unsigned j;
6fd602
+
6fd602
+	  /* Skip already handled handles  */
6fd602
+	  if (is_in_list (handle))
6fd602
+	    {
6fd602
+	      grub_dprintf ("efi", "  handle %p: already processed\n",
6fd602
+				   handle);
6fd602
+	      continue;
6fd602
+	    }
6fd602
+
6fd602
+	  status = grub_efi_connect_controller(handle, NULL, NULL,
6fd602
+			items[s].flags & SEARCHED_ITEM_FLAG_RECURSIVE ? 1 : 0);
6fd602
+	  if (status == GRUB_EFI_SUCCESS)
6fd602
+	    {
6fd602
+	      connected++;
6fd602
+	      total_connected++;
6fd602
+	      grub_dprintf ("efi", "  handle %p: connected\n", handle);
6fd602
+	    }
6fd602
+	  else
6fd602
+	    grub_dprintf ("efi", "  handle %p: failed to connect (%d)\n",
6fd602
+				 handle, (grub_efi_int8_t) status);
6fd602
+
6fd602
+	  if ((grub_err = add_handle (handle)) != GRUB_ERR_NONE)
6fd602
+	    break; /* fatal  */
6fd602
+	}
6fd602
+
6fd602
+      grub_free (handles);
6fd602
+      if (grub_err != GRUB_ERR_NONE)
6fd602
+	break; /* fatal  */
6fd602
+
6fd602
+      if (items[s].flags & SEARCHED_ITEM_FLAG_LOOP && connected)
6fd602
+	{
6fd602
+	  connected = 0;
6fd602
+	  goto loop;
6fd602
+	}
6fd602
+
6fd602
+      free_handle_list ();
6fd602
+    }
6fd602
+
6fd602
+  free_handle_list ();
6fd602
+
6fd602
+  if (total_connected)
6fd602
+    grub_efidisk_reenumerate_disks ();
6fd602
+
6fd602
+  return grub_err;
6fd602
+}
6fd602
+
6fd602
+static grub_command_t cmd;
6fd602
+
6fd602
+GRUB_MOD_INIT(connectefi)
6fd602
+{
6fd602
+  cmd = grub_register_command ("connectefi", grub_cmd_connectefi,
6fd602
+			       N_("pciroot|scsi"),
6fd602
+			       N_("Connect EFI handles."
6fd602
+				  " If 'pciroot' is specified, connect PCI"
6fd602
+				  " root EFI handles recursively."
6fd602
+				  " If 'scsi' is specified, connect SCSI"
6fd602
+				  " I/O EFI handles recursively."));
6fd602
+}
6fd602
+
6fd602
+GRUB_MOD_FINI(connectefi)
6fd602
+{
6fd602
+  grub_unregister_command (cmd);
6fd602
+}
6fd602
diff --git a/grub-core/commands/efi/lsefi.c b/grub-core/commands/efi/lsefi.c
b35c50
index d1ce99af43..f2d2430e66 100644
6fd602
--- a/grub-core/commands/efi/lsefi.c
6fd602
+++ b/grub-core/commands/efi/lsefi.c
6fd602
@@ -19,6 +19,7 @@
6fd602
 #include <grub/mm.h>
6fd602
 #include <grub/misc.h>
6fd602
 #include <grub/efi/api.h>
6fd602
+#include <grub/efi/disk.h>
6fd602
 #include <grub/efi/edid.h>
6fd602
 #include <grub/efi/pci.h>
6fd602
 #include <grub/efi/efi.h>
6fd602
diff --git a/grub-core/disk/efi/efidisk.c b/grub-core/disk/efi/efidisk.c
b35c50
index fe8ba6e6c9..062143dfff 100644
6fd602
--- a/grub-core/disk/efi/efidisk.c
6fd602
+++ b/grub-core/disk/efi/efidisk.c
6fd602
@@ -396,6 +396,19 @@ enumerate_disks (void)
6fd602
   free_devices (devices);
6fd602
 }
6fd602
 
6fd602
+void
6fd602
+grub_efidisk_reenumerate_disks (void)
6fd602
+{
6fd602
+  free_devices (fd_devices);
6fd602
+  free_devices (hd_devices);
6fd602
+  free_devices (cd_devices);
6fd602
+  fd_devices = 0;
6fd602
+  hd_devices = 0;
6fd602
+  cd_devices = 0;
6fd602
+
6fd602
+  enumerate_disks ();
6fd602
+}
6fd602
+
6fd602
 static int
6fd602
 grub_efidisk_iterate (grub_disk_dev_iterate_hook_t hook, void *hook_data,
6fd602
 		      grub_disk_pull_t pull)
6fd602
diff --git a/grub-core/kern/efi/efi.c b/grub-core/kern/efi/efi.c
b35c50
index 14bc10eb56..7fcca69c17 100644
6fd602
--- a/grub-core/kern/efi/efi.c
6fd602
+++ b/grub-core/kern/efi/efi.c
6fd602
@@ -95,6 +95,19 @@ grub_efi_locate_handle (grub_efi_locate_search_type_t search_type,
6fd602
   return buffer;
6fd602
 }
6fd602
 
6fd602
+grub_efi_status_t
6fd602
+grub_efi_connect_controller (grub_efi_handle_t controller_handle,
6fd602
+			     grub_efi_handle_t *driver_image_handle,
6fd602
+			     grub_efi_device_path_protocol_t *remaining_device_path,
6fd602
+			     grub_efi_boolean_t recursive)
6fd602
+{
6fd602
+  grub_efi_boot_services_t *b;
6fd602
+
6fd602
+  b = grub_efi_system_table->boot_services;
6fd602
+  return efi_call_4 (b->connect_controller, controller_handle,
6fd602
+		     driver_image_handle, remaining_device_path, recursive);
6fd602
+}
6fd602
+
6fd602
 void *
6fd602
 grub_efi_open_protocol (grub_efi_handle_t handle,
6fd602
 			grub_efi_guid_t *protocol,
6fd602
diff --git a/include/grub/efi/disk.h b/include/grub/efi/disk.h
b35c50
index 254475c842..6845c2f1fd 100644
6fd602
--- a/include/grub/efi/disk.h
6fd602
+++ b/include/grub/efi/disk.h
6fd602
@@ -27,6 +27,8 @@ grub_efi_handle_t
6fd602
 EXPORT_FUNC(grub_efidisk_get_device_handle) (grub_disk_t disk);
6fd602
 char *EXPORT_FUNC(grub_efidisk_get_device_name) (grub_efi_handle_t *handle);
6fd602
 
6fd602
+void EXPORT_FUNC(grub_efidisk_reenumerate_disks) (void);
6fd602
+
6fd602
 void grub_efidisk_init (void);
6fd602
 void grub_efidisk_fini (void);
6fd602
 
6fd602
diff --git a/include/grub/efi/efi.h b/include/grub/efi/efi.h
b35c50
index 8dfc89a33b..ec52083c49 100644
6fd602
--- a/include/grub/efi/efi.h
6fd602
+++ b/include/grub/efi/efi.h
6fd602
@@ -41,6 +41,11 @@ EXPORT_FUNC(grub_efi_locate_handle) (grub_efi_locate_search_type_t search_type,
6fd602
 				     grub_efi_guid_t *protocol,
6fd602
 				     void *search_key,
6fd602
 				     grub_efi_uintn_t *num_handles);
6fd602
+grub_efi_status_t
6fd602
+EXPORT_FUNC(grub_efi_connect_controller) (grub_efi_handle_t controller_handle,
6fd602
+					  grub_efi_handle_t *driver_image_handle,
6fd602
+					  grub_efi_device_path_protocol_t *remaining_device_path,
6fd602
+					  grub_efi_boolean_t recursive);
6fd602
 void *EXPORT_FUNC(grub_efi_open_protocol) (grub_efi_handle_t handle,
6fd602
 					   grub_efi_guid_t *protocol,
6fd602
 					   grub_efi_uint32_t attributes);
6fd602
diff --git a/NEWS b/NEWS
b35c50
index 73b8492bc4..d7c1d23aed 100644
6fd602
--- a/NEWS
6fd602
+++ b/NEWS
6fd602
@@ -98,7 +98,7 @@ New in 2.02:
6fd602
   * Prefer pmtimer for TSC calibration.
6fd602
 
6fd602
 * New/improved platform support:
6fd602
-  * New `efifwsetup' and `lsefi' commands on EFI platforms.
6fd602
+  * New `efifwsetup', `lsefi' and `connectefi` commands on EFI platforms.
6fd602
   * New `cmosdump' and `cmosset' commands on platforms with CMOS support.
6fd602
   * New command `pcidump' for PCI platforms.
6fd602
   * Improve opcode parsing in ACPI halt implementation.