[efi] Use device path to locate filesystem from which we were loaded

The file:/ URI syntax may be used to refer to local files on the
filesystem from which the iPXE binary was loaded.  This is currently
implemented by directly using the DeviceHandle recorded in our
EFI_LOADED_IMAGE_PROTOCOL.

This mechanism will fail when a USB-enabled build of iPXE is loaded
from USB storage and subsequently installs its own USB host controller
drivers, since doing so will disconnect and reconnect the existing USB
storage drivers and thereby invalidate the original storage device
handle.

Fix by recording the device path for the loaded image's DeviceHandle
at initialisation time and later using the recorded device path to
locate the appropriate device handle.

Signed-off-by: Michael Brown <mcb30@ipxe.org>
diff --git a/src/interface/efi/efi_init.c b/src/interface/efi/efi_init.c
index e1041a5..70212b1 100644
--- a/src/interface/efi/efi_init.c
+++ b/src/interface/efi/efi_init.c
@@ -26,6 +26,7 @@
 #include <ipxe/rotate.h>
 #include <ipxe/efi/efi.h>
 #include <ipxe/efi/efi_driver.h>
+#include <ipxe/efi/efi_utils.h>
 #include <ipxe/efi/Protocol/LoadedImage.h>
 
 /** Image handle passed to entry point */
@@ -34,6 +35,9 @@
 /** Loaded image protocol for this image */
 EFI_LOADED_IMAGE_PROTOCOL *efi_loaded_image;
 
+/** Device path for the loaded image's device handle */
+EFI_DEVICE_PATH_PROTOCOL *efi_loaded_image_path;
+
 /** System table passed to entry point
  *
  * We construct the symbol name efi_systab via the PLATFORM macro.
@@ -152,6 +156,9 @@
 	struct efi_protocol *prot;
 	struct efi_config_table *tab;
 	void *loaded_image;
+	void *device_path;
+	void *device_path_copy;
+	size_t device_path_len;
 	EFI_STATUS efirc;
 	int rc;
 
@@ -230,6 +237,33 @@
 	DBGC ( systab, "EFI image base address %p\n",
 	       efi_loaded_image->ImageBase );
 
+	/* Get loaded image's device handle's device path */
+	if ( ( efirc = bs->OpenProtocol ( efi_loaded_image->DeviceHandle,
+				&efi_device_path_protocol_guid,
+				&device_path, image_handle, NULL,
+				EFI_OPEN_PROTOCOL_GET_PROTOCOL ) ) != 0 ) {
+		rc = -EEFI ( efirc );
+		DBGC ( systab, "EFI could not get loaded image's device path: "
+		       "%s", strerror ( rc ) );
+		goto err_no_device_path;
+	}
+
+	/* Make a copy of the loaded image's device handle's device
+	 * path, since the device handle itself may become invalidated
+	 * when we load our own drivers.
+	 */
+	device_path_len = ( efi_devpath_len ( device_path ) +
+			    sizeof ( EFI_DEVICE_PATH_PROTOCOL ) );
+	if ( ( efirc = bs->AllocatePool ( EfiBootServicesData, device_path_len,
+					  &device_path_copy ) ) != 0 ) {
+		rc = -EEFI ( efirc );
+		goto err_alloc_device_path;
+	}
+	memcpy ( device_path_copy, device_path, device_path_len );
+	efi_loaded_image_path = device_path_copy;
+	DBGC ( systab, "EFI image device path %s\n",
+	       efi_devpath_text ( efi_loaded_image_path ) );
+
 	/* EFI is perfectly capable of gracefully shutting down any
 	 * loaded devices if it decides to fall back to a legacy boot.
 	 * For no particularly comprehensible reason, it doesn't
@@ -261,6 +295,9 @@
  err_driver_install:
 	bs->CloseEvent ( efi_shutdown_event );
  err_create_event:
+	bs->FreePool ( efi_loaded_image_path );
+ err_alloc_device_path:
+ err_no_device_path:
  err_no_loaded_image:
  err_missing_table:
  err_missing_protocol:
@@ -291,6 +328,9 @@
 	/* Uninstall exit boot services event */
 	bs->CloseEvent ( efi_shutdown_event );
 
+	/* Free copy of loaded image's device handle's device path */
+	bs->FreePool ( efi_loaded_image_path );
+
 	DBGC ( systab, "EFI image unloaded\n" );
 
 	return 0;
diff --git a/src/interface/efi/efi_local.c b/src/interface/efi/efi_local.c
index bd010ad..79ea822 100644
--- a/src/interface/efi/efi_local.c
+++ b/src/interface/efi/efi_local.c
@@ -307,6 +307,7 @@
 	EFI_GUID *protocol = &efi_simple_file_system_protocol_guid;
 	int ( * check ) ( struct efi_local *local, EFI_HANDLE device,
 			  EFI_FILE_PROTOCOL *root, const char *volume );
+	EFI_DEVICE_PATH_PROTOCOL *path;
 	EFI_FILE_PROTOCOL *root;
 	EFI_HANDLE *handles;
 	EFI_HANDLE device;
@@ -328,8 +329,18 @@
 		}
 		check = efi_local_check_volume_name;
 	} else {
-		/* Use our loaded image's device handle */
-		handles = &efi_loaded_image->DeviceHandle;
+		/* Locate filesystem from which we were loaded */
+		path = efi_loaded_image_path;
+		if ( ( efirc = bs->LocateDevicePath ( protocol, &path,
+						      &device ) ) != 0 ) {
+			rc = -EEFI ( efirc );
+			DBGC ( local, "LOCAL %p could not locate file system "
+			       "on %s: %s\n", local,
+			       efi_devpath_text ( efi_loaded_image_path ),
+			       strerror ( rc ) );
+			return rc;
+		}
+		handles = &device;
 		num_handles = 1;
 		check = NULL;
 	}