[efi] Install protocols onto a dedicated device handle

When we fall back to using our own loaded image's device handle
(instead of the most recently opened SNP device handle), we may find
that some protocols (e.g. EFI_SIMPLE_FILE_SYSTEM_PROTOCOL) are already
present on this handle.

Fix by creating a child device handle with an added Uri() device path
component, and installing EFI_SIMPLE_FILE_SYSTEM_PROTOCOL and others
onto this handle instead of onto the identified parent device handle.

This also provides a way for us to communicate the image URI to a
chainloaded iPXE, so that that iPXE can set its current working URI
appropriately.

A side effect of this change is that the EFI_SIMPLE_NETWORK_PROTOCOL
will be found on the parent of the loaded image's device handle,
rather than directly on the loaded image's device handle.  This will
not cause problems for a chainloaded iPXE, since that will already use
LocateDevicePath() to find EFI_SIMPLE_NETWORK_PROTOCOL (or the
EFI_MANAGED_NETWORK_SERVICE_BINDING_PROTOCOL created by MnpDxe) and so
will already find the instance on the parent device handle.  If other
UEFI executables are found to exist that do require the protocols to
be installed directly on the loaded image's device handle, then we
could potentially install copies of these protocol instances on the
device handle.

Signed-off-by: Michael Brown <mcb30@ipxe.org>
This commit is contained in:
Michael Brown
2026-03-05 12:10:15 +00:00
parent 910d9c5a98
commit 5a17d8daf3
+103 -13
View File
@@ -36,6 +36,7 @@ FILE_SECBOOT ( PERMITTED );
#include <ipxe/efi/efi_image.h>
#include <ipxe/efi/efi_shim.h>
#include <ipxe/efi/efi_fdt.h>
#include <ipxe/efi/efi_utils.h>
#include <ipxe/image.h>
#include <ipxe/init.h>
#include <ipxe/features.h>
@@ -61,7 +62,7 @@ FEATURE ( FEATURE_IMAGE, "EFI", DHCP_EB_FEATURE_EFI, 1 );
#define EEFI_START( efirc ) EPLATFORM ( EINFO_EEFI_START, efirc )
/**
* Create device path for image
* Create a URI device path for image
*
* @v image EFI image
* @v parent Parent device path
@@ -70,7 +71,51 @@ FEATURE ( FEATURE_IMAGE, "EFI", DHCP_EB_FEATURE_EFI, 1 );
* The caller must eventually free() the device path.
*/
static EFI_DEVICE_PATH_PROTOCOL *
efi_image_path ( struct image *image, EFI_DEVICE_PATH_PROTOCOL *parent ) {
efi_image_uripath ( struct image *image, EFI_DEVICE_PATH_PROTOCOL *parent ) {
EFI_DEVICE_PATH_PROTOCOL *path;
URI_DEVICE_PATH *uripath;
EFI_DEVICE_PATH_PROTOCOL *end;
size_t uri_len;
size_t prefix_len;
size_t uripath_len;
size_t len;
/* Calculate device path lengths */
prefix_len = efi_path_len ( parent );
uri_len = format_uri ( image->uri, NULL, 0 );
uripath_len = ( sizeof ( *uripath ) + uri_len + 1 /* NUL */ );
len = ( prefix_len + uripath_len + sizeof ( *end ) );
/* Allocate device path */
path = zalloc ( len );
if ( ! path )
return NULL;
/* Construct device path */
memcpy ( path, parent, prefix_len );
uripath = ( ( ( void * ) path ) + prefix_len );
uripath->Header.Type = MESSAGING_DEVICE_PATH;
uripath->Header.SubType = MSG_URI_DP;
uripath->Header.Length[0] = ( uripath_len & 0xff );
uripath->Header.Length[1] = ( uripath_len >> 8 );
format_uri ( image->uri, uripath->Uri, ( uri_len + 1 /* NUL */ ) );
end = ( ( ( void * ) uripath ) + uripath_len );
efi_path_terminate ( end );
return path;
}
/**
* Create a file device path for image
*
* @v image EFI image
* @v parent Parent device path
* @ret path Device path, or NULL on failure
*
* The caller must eventually free() the device path.
*/
static EFI_DEVICE_PATH_PROTOCOL *
efi_image_filepath ( struct image *image, EFI_DEVICE_PATH_PROTOCOL *parent ) {
EFI_DEVICE_PATH_PROTOCOL *path;
FILEPATH_DEVICE_PATH *filepath;
EFI_DEVICE_PATH_PROTOCOL *end;
@@ -154,11 +199,13 @@ static int efi_image_exec ( struct image *image ) {
EFI_BOOT_SERVICES *bs = efi_systab->BootServices;
struct efi_snp_device *snpdev;
struct net_device *netdev;
EFI_DEVICE_PATH_PROTOCOL *basepath;
EFI_DEVICE_PATH_PROTOCOL *devpath;
EFI_DEVICE_PATH_PROTOCOL *imgpath;
EFI_LOADED_IMAGE_PROTOCOL *loaded;
struct image *shim;
struct image *exec;
EFI_HANDLE parent;
EFI_HANDLE device;
EFI_HANDLE handle;
EFI_MEMORY_TYPE type;
@@ -167,24 +214,56 @@ static int efi_image_exec ( struct image *image ) {
EFI_STATUS efirc;
int rc;
/* Find an appropriate device handle to use */
/* Find an appropriate parent device handle to use */
snpdev = last_opened_snpdev();
if ( snpdev ) {
/* We have a netX: use this as the base device */
device = snpdev->handle;
devpath = snpdev->path;
parent = snpdev->handle;
basepath = snpdev->path;
netdev = netdev_get ( snpdev->netdev );
DBGC ( image, "EFIIMAGE %s using %s %s\n", image->name,
netdev->name, efi_devpath_text ( devpath ) );
netdev->name, efi_devpath_text ( basepath ) );
} else {
/* We have no netX: fall back to using our own loaded
* image's device.
*/
device = efi_loaded_image->DeviceHandle;
devpath = efi_loaded_image_path;
parent = efi_loaded_image->DeviceHandle;
basepath = efi_loaded_image_path;
netdev = NULL;
DBGC ( image, "EFIIMAGE %s using %s\n",
image->name, efi_devpath_text ( devpath ) );
image->name, efi_devpath_text ( basepath ) );
}
/* Construct URI device path */
devpath = efi_image_uripath ( image, basepath );
if ( ! devpath ) {
DBGC ( image, "EFIIMAGE %s could not create URI device path\n",
image->name );
rc = -ENOMEM;
goto err_device_path;
}
/* Create device handle */
device = NULL;
if ( ( efirc = bs->InstallMultipleProtocolInterfaces (
&device,
&efi_device_path_protocol_guid, devpath,
NULL ) ) != 0 ) {
rc = -EEFI ( efirc );
DBGC ( image, "EFIIMAGE %s could not install %s: %s\n",
image->name, efi_devpath_text ( devpath ),
strerror ( rc ) );
goto err_path_install;
}
DBGC ( image, "EFIIMAGE %s device is %s\n",
image->name, efi_handle_name ( device ) );
/* Add as a child of the parent device */
if ( ( rc = efi_child_add ( parent, device ) ) != 0 ) {
DBGC ( image, "EFIIMAGE %s could not become child of %s: %s\n",
image->name, efi_handle_name ( parent ),
strerror ( rc ) );
goto err_child_add;
}
/* Use shim instead of directly executing image if applicable */
@@ -231,14 +310,16 @@ static int efi_image_exec ( struct image *image ) {
goto err_fdt_install;
}
/* Create device path for image */
imgpath = efi_image_path ( exec, devpath );
/* Create file device path for image */
imgpath = efi_image_filepath ( exec, devpath );
if ( ! imgpath ) {
DBGC ( image, "EFIIMAGE %s could not create device path\n",
image->name );
DBGC ( image, "EFIIMAGE %s could not create file device "
"path\n", image->name );
rc = -ENOMEM;
goto err_image_path;
}
DBGC ( image, "EFIIMAGE %s file is %s\n",
image->name, efi_devpath_text ( imgpath ) );
/* Create command line for image */
cmdline = efi_image_cmdline ( image );
@@ -373,6 +454,15 @@ static int efi_image_exec ( struct image *image ) {
unregister_image ( image );
err_register_image:
image->flags ^= toggle;
efi_child_del ( parent, device );
err_child_add:
bs->UninstallMultipleProtocolInterfaces (
device,
&efi_device_path_protocol_guid, devpath,
NULL );
err_path_install:
free ( devpath );
err_device_path:
netdev_put ( netdev );
return rc;
}