[ipxe-devel] [PATCH 3/3 v2] Complete implementation of efi_boot.c

Aaron Young Aaron.Young at oracle.com
Wed Feb 6 00:47:04 UTC 2019


Complete implementation of efi_boot.c which creates a static
map of available UEFI filesystems (i.e. "drives"). The map can be displayed
via efi_boot_display_map() which is used by the new efimap command. The
drives can be booted via efi_boot_local() which is called indirectly by the
sanboot command (via efi_block_boot()).

Note the map is ordered by device path (using strncmp()) which
will typically give a PCI BDF ordering as the device paths
typically begin with PciRoot()/Pci() nodes. This will help
maintain consistency between boots.

Also note that all PCI Bridges are "connected" so that all
UEFI PCI filesystems can be discovered. This works around
UEFI implementations which optimize the devices which are
automatically connected to the minimal set required to boot.

Signed-off-by: Aaron Young <Aaron.Young at oracle.com>
---
 src/interface/efi/efi_boot.c | 434 ++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 430 insertions(+), 4 deletions(-)

diff --git a/src/interface/efi/efi_boot.c b/src/interface/efi/efi_boot.c
index 5d44f8b..0017841 100644
--- a/src/interface/efi/efi_boot.c
+++ b/src/interface/efi/efi_boot.c
@@ -32,18 +32,444 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
 
 #include <stddef.h>
 #include <stdlib.h>
+#include <stdio.h>
 #include <string.h>
 #include <errno.h>
+#include <ipxe/refcnt.h>
+#include <ipxe/list.h>
+#include <ipxe/uri.h>
+#include <ipxe/interface.h>
+#include <ipxe/blockdev.h>
+#include <ipxe/xfer.h>
+#include <ipxe/open.h>
+#include <ipxe/retry.h>
+#include <ipxe/timer.h>
+#include <ipxe/process.h>
+#include <ipxe/sanboot.h>
+#include <ipxe/iso9660.h>
+#include <ipxe/acpi.h>
+#include <ipxe/efi/efi.h>
+#include <ipxe/efi/efi_driver.h>
+#include <ipxe/efi/efi_strings.h>
+#include <ipxe/efi/efi_snp.h>
+#include <ipxe/efi/efi_utils.h>
+#include <ipxe/efi/efi_block.h>
+#include <ipxe/efi/Guid/FileInfo.h>
+#include <ipxe/efi/Guid/FileSystemInfo.h>
+#include <ipxe/efi/Protocol/BlockIo.h>
+#include <ipxe/efi/Protocol/SimpleFileSystem.h>
+#include <ipxe/efi/Protocol/AcpiTable.h>
+
+static wchar_t efi_default_boot_filename[] = EFI_REMOVABLE_MEDIA_FILE_NAME;
+
+static EFI_DEVICE_PATH_PROTOCOL	**DevicePathList;
+static UINTN			  DevicePathListNum;
+static EFI_HANDLE		 *SimpleFSHandleList;
+static BOOLEAN			  efi_boot_map_initialized = FALSE;
+
+static EFI_HANDLE * efi_boot_get_handlelist ( EFI_GUID *ProtocolGuid ) {
+	EFI_BOOT_SERVICES	*bs = efi_systab->BootServices;
+	EFI_HANDLE		*HandleList;
+	UINTN			 Size;
+	EFI_STATUS		 efirc;
+	EFI_LOCATE_SEARCH_TYPE	 SearchType;
+
+	/* NULL ProtocolGuid gets all handles in system */
+	if ( ProtocolGuid )
+		SearchType = ByProtocol;
+	else
+		SearchType = AllHandles;
+
+	Size = 0;
+	HandleList = NULL;
+
+	/* First call gets the handle list size and returns BUFFER_TOO_SMALL. */
+	efirc = bs->LocateHandle ( SearchType, (EFI_GUID*) ProtocolGuid,
+				 NULL, &Size, HandleList );
+	if ( efirc == EFI_BUFFER_TOO_SMALL ) {
+		/* Alloc an extra handle for NULL list terminator */
+		if ( ( efirc = bs->AllocatePool ( EfiBootServicesData,
+					          ( Size +
+						    sizeof ( EFI_HANDLE ) ),
+					          (void **) &HandleList ) )
+					          != 0 ) {
+			return ( NULL ) ;
+		}
+
+		efirc = bs->LocateHandle ( SearchType, (EFI_GUID*) ProtocolGuid,
+					 NULL, &Size, HandleList );
+		if ( HandleList )
+			HandleList[Size / sizeof ( EFI_HANDLE )] = NULL;
+	}
+
+	if ( EFI_ERROR ( efirc ) ) {
+		if ( HandleList )
+			bs->FreePool ( HandleList );
+		return ( NULL ) ;
+	}
+
+	return ( HandleList );
+}
+
+static EFI_DEVICE_PATH_PROTOCOL * efi_boot_get_devpath ( EFI_HANDLE Handle ) {
+	EFI_BOOT_SERVICES		*bs = efi_systab->BootServices;
+	EFI_DEVICE_PATH_PROTOCOL	*DevicePath;
+	EFI_STATUS			 efirc;
+
+	efirc = bs->HandleProtocol ( Handle, &efi_device_path_protocol_guid,
+				    (VOID *)  &DevicePath );
+
+	if ( EFI_ERROR ( efirc ) )
+		return NULL;
+	else
+		return DevicePath;
+}
+
+static void efi_boot_connect_pcibridges ( void ) {
+	EFI_BOOT_SERVICES	*bs = efi_systab->BootServices;
+	EFI_HANDLE		*HandleList;
+	UINTN			 Count;
+
+	HandleList = efi_boot_get_handlelist ( &efi_pci_root_bridge_io_protocol_guid );
+	if ( HandleList == NULL ) {
+		DBG ( "EFIBOOT efi_boot_connect_pcibridges: no handles!\n" );
+		return;
+	}
+
+	for ( Count = 0 ; HandleList[Count] != NULL ; Count++ ) {
+
+		DBG ( "EFIBOOT efi_boot_connect_pcibridges: connecting "
+		      "handle %s\n", efi_handle_name ( HandleList[Count] ) );
+
+		 (void)  bs->ConnectController ( HandleList[Count], NULL,
+						 NULL, 1 );
+
+		DBG ( "EFIBOOT: handle %s supports protocols:\n",
+		      efi_handle_name ( HandleList[Count] ) );
+		DBG_EFI_PROTOCOLS_IF ( LOG, HandleList[Count] );
+	}
+
+	bs->FreePool ( HandleList );
 
-void efi_boot_display_map ( void ) {
 	return;
 }
 
+static int efi_vol_label( EFI_HANDLE handle, char *label_buf,
+			     size_t label_buf_size ) {
+	EFI_BOOT_SERVICES *bs = efi_systab->BootServices;
+	EFI_FILE_SYSTEM_INFO *info;
+	EFI_FILE_PROTOCOL *root;
+	EFI_STATUS efirc;
+	UINTN size;
+	int rc;
+	union {
+		void *interface;
+		EFI_SIMPLE_FILE_SYSTEM_PROTOCOL *fs;
+	} u;
+
+	/* Open file system protocol */
+	if ( ( efirc = bs->OpenProtocol ( handle,
+					  &efi_simple_file_system_protocol_guid,
+					  &u.interface, efi_image_handle,
+					  handle,
+					  EFI_OPEN_PROTOCOL_GET_PROTOCOL ))!=0){
+		rc = -1;
+		DBG ( "Could not open filesystem on %s\n",
+		      efi_handle_name ( handle ) );
+		goto err_filesystem;
+	}
+
+	/* Open root directory */
+	if ( ( efirc = u.fs->OpenVolume ( u.fs, &root ) ) != 0 ) {
+		rc = -1;
+		DBG ( "Could not open volume on %s\n",
+		      efi_handle_name ( handle ) );
+		goto err_volume;
+	}
+
+	/* Get length of file system information */
+	size = 0;
+	root->GetInfo ( root, &efi_file_system_info_id, &size, NULL );
+
+	/* Allocate file system information */
+	info = malloc ( size );
+	if ( ! info ) {
+		rc = -1;
+		goto err_alloc_info;
+	}
+
+	/* Get file system information */
+	if ( ( efirc = root->GetInfo ( root, &efi_file_system_info_id, &size,
+				       info ) ) != 0 ) {
+		rc = -1;
+		DBG ( "could not get file system info on %s\n",
+		      efi_handle_name ( handle ) );
+		goto err_get_info;
+	}
+	DBG ( "Found %s with label \"%ls\"\n",
+	      efi_handle_name ( handle ), info->VolumeLabel );
+
+	snprintf ( label_buf, label_buf_size, "%ls", info->VolumeLabel );
+
+	/* Success */
+	rc = 0;
+
+ err_get_info:
+	free ( info );
+ err_alloc_info:
+	root->Close ( root );
+ err_volume:
+	bs->CloseProtocol ( handle, &efi_simple_file_system_protocol_guid,
+			     efi_image_handle, handle );
+ err_filesystem:
+	return rc;
+}
+
+static int efi_boot_create_map ( void ) {
+	EFI_BOOT_SERVICES		*bs = efi_systab->BootServices;
+	UINTN				 Count;
+	UINTN				 i,j;
+	INTN				 NextIndex;
+	EFI_STATUS			 efirc;
+	EFI_HANDLE			*TmpSimpleFSHandleList;
+	EFI_DEVICE_PATH_PROTOCOL	**TmpDevicePathList;
+	VOID				*buffer;
+	const char			*path2;
+	char				 path1buf[256]; // 256 is the max buf
+							// size used internally
+							// by efi_devpath_text()
+
+	DevicePathList = NULL;
+	DevicePathListNum = 0;
+	SimpleFSHandleList = NULL;
+
+	efi_boot_connect_pcibridges ();
+
+	TmpSimpleFSHandleList = efi_boot_get_handlelist ( &efi_simple_file_system_protocol_guid );
+	if ( TmpSimpleFSHandleList == NULL ) {
+		/* valid - no filesystems found */
+		efi_boot_map_initialized = TRUE;
+		return 0;
+	}
+
+	/* Count number of handles */
+	for ( Count = 0 ; TmpSimpleFSHandleList[Count] != NULL ; Count++ );
+
+	/* Allocate our temporary/local device path list */
+	if ( ( efirc = bs->AllocatePool ( EfiBootServicesData,
+				          ( Count *
+                                            sizeof ( EFI_DEVICE_PATH_PROTOCOL* )
+					   ),
+					  &buffer ) ) != 0 ) {
+		DBG ( "EFIBOOT efi_boot_create_map: AllocatePool failed!\n" );
+		bs->FreePool ( TmpSimpleFSHandleList );
+		efi_boot_map_initialized = TRUE;
+		return -1;
+	}
+	TmpDevicePathList = (EFI_DEVICE_PATH_PROTOCOL **) buffer;
+
+	/* Populate the devpath list */
+	for ( i = 0 ; i < Count; i++ ) {
+		TmpDevicePathList[i] = efi_boot_get_devpath ( TmpSimpleFSHandleList[i] );
+	}
+
+	/* Allocate our global device path list */
+	if ( ( efirc = bs->AllocatePool ( EfiBootServicesData,
+				          ( Count *
+					    sizeof ( EFI_DEVICE_PATH_PROTOCOL* )
+					  ),
+					  &buffer ) ) != 0 ) {
+		DBG ( "EFIBOOT efi_boot_create_map: AllocatePool failed!\n" );
+		bs->FreePool ( TmpSimpleFSHandleList );
+		bs->FreePool ( TmpDevicePathList );
+		efi_boot_map_initialized = TRUE;
+
+		return -1;
+	}
+	DevicePathList = (EFI_DEVICE_PATH_PROTOCOL **) buffer;
+
+	/* Allocate our global SimpleFSHandle list */
+	if ( ( efirc = bs->AllocatePool ( EfiBootServicesData,
+				          ( Count *
+					    sizeof ( EFI_HANDLE )
+					  ),
+					  &buffer ) ) != 0 ) {
+		DBG ( "EFIBOOT efi_boot_create_map: AllocatePool failed!\n" );
+		bs->FreePool ( DevicePathList );
+		DevicePathList = NULL;
+		bs->FreePool ( TmpSimpleFSHandleList );
+		bs->FreePool ( TmpDevicePathList );
+		efi_boot_map_initialized = TRUE;
+
+		return -1;
+	}
+	SimpleFSHandleList = (EFI_HANDLE *) buffer;
+
+	/*
+	 * Populate the global SimpleFSHandle and DevicePath list.
+	 * For consistency, order the list.
+	 * Since each device path begins with PciRoot()/Pci() nodes,
+	 * this will essentially give PCI BDF ordering.
+	 * Put NULL devicepaths at end of the list (should not happen).
+	 */
+	for ( i = 0; i < Count; i++ ) {
+		NextIndex = -1;
+		path1buf[0]='\0';
+		for ( j = 0; j < Count; j++ ) {
+			if ( TmpDevicePathList[j] == NULL )
+				continue;
+			path2 = efi_devpath_text ( TmpDevicePathList[j] );
+			if ( !path2 )
+				continue;
+
+			DBG ( "EFIBOOT %d: next=%d, comparing %s to %s\n",
+			     (int) i, (int) NextIndex, path2, path1buf );
+			if ( NextIndex == -1 || strncmp ( path2, path1buf,
+							  256 )  < 0 ) {
+				NextIndex = j;
+				strncpy ( path1buf, path2, 256 );
+			}
+		}
+		if ( NextIndex != -1 ) {
+			DevicePathList[i] = TmpDevicePathList[NextIndex];
+			SimpleFSHandleList[i] = TmpSimpleFSHandleList[NextIndex];
+			TmpDevicePathList[NextIndex] = NULL;
+		} else {
+			DevicePathList[i] = NULL;
+		}
+	}
+
+	DevicePathListNum = Count;
+
+	bs->FreePool ( TmpSimpleFSHandleList );
+	bs->FreePool ( TmpDevicePathList );
+
+	efi_boot_map_initialized = TRUE;
+
+	return 0;
+}
+
+static int efi_boot_local_fs ( EFI_DEVICE_PATH_PROTOCOL *dp,
+			       const char *filename ) {
+	EFI_BOOT_SERVICES		*bs = efi_systab->BootServices;
+	EFI_DEVICE_PATH_PROTOCOL	*boot_path;
+	FILEPATH_DEVICE_PATH		*filepath;
+	EFI_DEVICE_PATH_PROTOCOL	*end;
+	size_t				 prefix_len;
+	size_t				 filepath_len;
+	size_t				 boot_path_len;
+	EFI_HANDLE			 image = NULL;
+	EFI_STATUS			 efirc;
+	int				 rc;
+
+	if ( dp == NULL )
+		return -1;
+
+	DBG ( "EFIBOOT efi_boot_local_fs: device path %s\n",
+	      efi_devpath_text ( dp ) );
+
+	/* Construct device path for boot image */
+	end = efi_devpath_end ( dp );
+	prefix_len =  ( ( (void *) end ) - ( (void *) dp ) );
+	filepath_len = ( SIZE_OF_FILEPATH_DEVICE_PATH +
+			 ( filename ? ( ( strlen ( filename ) + 1 ) *
+			 sizeof ( filepath->PathName[0] ) ):
+			 sizeof ( efi_default_boot_filename ) ) );
+
+	boot_path_len = ( prefix_len + filepath_len + sizeof ( *end ) );
+	boot_path = zalloc ( boot_path_len );
+	if ( !boot_path ) {
+		rc = -1;
+		goto err_alloc_path;
+	}
+
+	memcpy ( boot_path, dp, prefix_len );
+	filepath = ( ( (void *) boot_path ) + prefix_len );
+	filepath->Header.Type = MEDIA_DEVICE_PATH;
+	filepath->Header.SubType = MEDIA_FILEPATH_DP;
+	filepath->Header.Length[0] = ( filepath_len & 0xff );
+	filepath->Header.Length[1] = ( filepath_len >> 8 );
+
+	if ( filename ) {
+		efi_sprintf ( filepath->PathName, "%s", filename );
+	} else {
+		memcpy ( filepath->PathName, efi_default_boot_filename,
+		         sizeof ( efi_default_boot_filename ) );
+	}
+
+	end = ( ( (void *) filepath ) + filepath_len );
+	end->Type = END_DEVICE_PATH_TYPE;
+	end->SubType = END_ENTIRE_DEVICE_PATH_SUBTYPE;
+	end->Length[0] = sizeof ( *end );
+
+	/* Release SNP devices */
+	efi_snp_release ();
+
+	DBG ( "EFIBOOT attempt to load %s\n", efi_devpath_text ( boot_path ) );
+
+	if ( ( efirc = bs->LoadImage ( FALSE, efi_image_handle, boot_path,
+				       NULL, 0, &image ) ) != 0 ) {
+		rc = -1;
+		DBG ( "EFIBOOT failed to load image\n" );
+		goto err_load_image;
+	}
+
+	DBG ( "EFIBOOT successfully loaded image\n" );
+	DBG ( "EFIBOOT trying to start %s\n",
+	      efi_devpath_text ( boot_path ) );
+
+	efirc = bs->StartImage ( image, NULL, NULL );
+	if ( EFI_ERROR ( efirc ) )
+		rc = -1;
+	else
+		rc = 0;
+
+	DBG ( "EFIBOOT boot image returned: %d\n", rc );
+
+	bs->UnloadImage ( image );
+
+err_load_image:
+	efi_snp_claim ();
+	free ( boot_path );
+err_alloc_path:
+
+	return rc;
+}
+
+void efi_boot_display_map ( void ) {
+	char vol_label[256];
+	UINTN i;
+	int rc;
+
+	if ( !efi_boot_map_initialized )
+		efi_boot_create_map ();
+
+	printf ( "Drive#\t[Volume Label] Path\n" );
+	printf ( "------\t-------------------\n" );
+	for ( i = 0 ; i < DevicePathListNum ; i++ ) {
+		if ( DevicePathList[i] != NULL ) {
+			rc = efi_vol_label ( SimpleFSHandleList[i],
+					     vol_label, 256 );
+			if ( rc || *vol_label == '\0' )
+				strcpy(vol_label, "NO VOLUME LABEL");
+			printf ( "%d     \t[%s] %s\n", (int) i, vol_label,
+				efi_devpath_text ( DevicePathList[i] ) );
+		}
+	}
+}
+
 int efi_boot_local ( unsigned int drive, const char *filename ) {
 
-	// Dummy code to get around compiler for now.
-	if (filename)
-		return drive;
+	if ( !efi_boot_map_initialized )
+		efi_boot_create_map ();
+
+	if ( DevicePathListNum == 0 || drive > ( DevicePathListNum-1 ) ||
+	    DevicePathList[drive] == NULL ) {
+		printf ( "ERROR: Invalid drive number %#02x\n", drive );
+		return -1;
+	}
+
+	efi_boot_local_fs ( DevicePathList[drive], filename );
 
 	return 0;
 }
-- 
1.8.3.1




More information about the ipxe-devel mailing list