/* * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include 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; }