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