diff options
Diffstat (limited to 'src/interface/efi/efi_boot.c')
-rw-r--r-- | src/interface/efi/efi_boot.c | 475 |
1 files changed, 475 insertions, 0 deletions
diff --git a/src/interface/efi/efi_boot.c b/src/interface/efi/efi_boot.c new file mode 100644 index 00000000..19c31886 --- /dev/null +++ b/src/interface/efi/efi_boot.c @@ -0,0 +1,475 @@ +/* + * Copyright (C) 2019 Oracle. All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * You can also choose to distribute this program under the terms of + * the Unmodified Binary Distribution Licence (as given in the file + * COPYING.UBDL), provided that you have satisfied its requirements. + */ + +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); + +/** + * @file + * + * EFI boot local protocols + * + */ + +#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_path.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 ); + + 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_path_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 ) { + + 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; +} |