| /* | |
| * Copyright (C) 2016 Michael Brown <mbrown@fensystems.co.uk>. | |
| * | |
| * 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_SECBOOT ( PERMITTED ); | |
| /** | |
| * @file | |
| * | |
| * EFI block device protocols | |
| * | |
| */ | |
| #include <stddef.h> | |
| #include <stdlib.h> | |
| #include <stdio.h> | |
| #include <string.h> | |
| #include <strings.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/Protocol/BlockIo.h> | |
| #include <ipxe/efi/Protocol/SimpleFileSystem.h> | |
| #include <ipxe/efi/Protocol/AcpiTable.h> | |
| #include <ipxe/efi/Guid/FileSystemInfo.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_null.h> | |
| #include <ipxe/efi/efi_wrap.h> | |
| #include <ipxe/efi/efi_block.h> | |
| /** ACPI table protocol protocol */ | |
| static EFI_ACPI_TABLE_PROTOCOL *acpi; | |
| EFI_REQUEST_PROTOCOL ( EFI_ACPI_TABLE_PROTOCOL, &acpi ); | |
| /** Boot filename */ | |
| static wchar_t efi_block_boot_filename[] = EFI_REMOVABLE_MEDIA_FILE_NAME; | |
| /** EFI SAN device private data */ | |
| struct efi_block_data { | |
| /** SAN device */ | |
| struct san_device *sandev; | |
| /** EFI handle */ | |
| EFI_HANDLE handle; | |
| /** Media descriptor */ | |
| EFI_BLOCK_IO_MEDIA media; | |
| /** Block I/O protocol */ | |
| EFI_BLOCK_IO_PROTOCOL block_io; | |
| /** Device path protocol */ | |
| EFI_DEVICE_PATH_PROTOCOL *path; | |
| }; | |
| /** | |
| * Read from or write to EFI block device | |
| * | |
| * @v sandev SAN device | |
| * @v lba Starting LBA | |
| * @v data Data buffer | |
| * @v len Size of buffer | |
| * @v sandev_rw SAN device read/write method | |
| * @ret rc Return status code | |
| */ | |
| static int efi_block_rw ( struct san_device *sandev, uint64_t lba, | |
| void *data, size_t len, | |
| int ( * sandev_rw ) ( struct san_device *sandev, | |
| uint64_t lba, | |
| unsigned int count, | |
| void *buffer ) ) { | |
| struct efi_block_data *block = sandev->priv; | |
| unsigned int count; | |
| int rc; | |
| /* Sanity check */ | |
| count = ( len / block->media.BlockSize ); | |
| if ( ( count * block->media.BlockSize ) != len ) { | |
| DBGC ( sandev->drive, "EFIBLK %#02x impossible length %#zx\n", | |
| sandev->drive, len ); | |
| return -EINVAL; | |
| } | |
| /* Read from / write to block device */ | |
| if ( ( rc = sandev_rw ( sandev, lba, count, data ) ) != 0 ) { | |
| DBGC ( sandev->drive, "EFIBLK %#02x I/O failed: %s\n", | |
| sandev->drive, strerror ( rc ) ); | |
| return rc; | |
| } | |
| return 0; | |
| } | |
| /** | |
| * Reset EFI block device | |
| * | |
| * @v block_io Block I/O protocol | |
| * @v verify Perform extended verification | |
| * @ret efirc EFI status code | |
| */ | |
| static EFI_STATUS EFIAPI efi_block_io_reset ( EFI_BLOCK_IO_PROTOCOL *block_io, | |
| BOOLEAN verify __unused ) { | |
| struct efi_block_data *block = | |
| container_of ( block_io, struct efi_block_data, block_io ); | |
| struct san_device *sandev = block->sandev; | |
| int rc; | |
| DBGC2 ( sandev->drive, "EFIBLK %#02x reset\n", sandev->drive ); | |
| efi_snp_claim(); | |
| rc = sandev_reset ( sandev ); | |
| efi_snp_release(); | |
| return EFIRC ( rc ); | |
| } | |
| /** | |
| * Read from EFI block device | |
| * | |
| * @v block_io Block I/O protocol | |
| * @v media Media identifier | |
| * @v lba Starting LBA | |
| * @v len Size of buffer | |
| * @v data Data buffer | |
| * @ret efirc EFI status code | |
| */ | |
| static EFI_STATUS EFIAPI | |
| efi_block_io_read ( EFI_BLOCK_IO_PROTOCOL *block_io, UINT32 media __unused, | |
| EFI_LBA lba, UINTN len, VOID *data ) { | |
| struct efi_block_data *block = | |
| container_of ( block_io, struct efi_block_data, block_io ); | |
| struct san_device *sandev = block->sandev; | |
| int rc; | |
| DBGC2 ( sandev->drive, "EFIBLK %#02x read LBA %#08llx to %p+%#08zx\n", | |
| sandev->drive, lba, data, ( ( size_t ) len ) ); | |
| efi_snp_claim(); | |
| rc = efi_block_rw ( sandev, lba, data, len, sandev_read ); | |
| efi_snp_release(); | |
| return EFIRC ( rc ); | |
| } | |
| /** | |
| * Write to EFI block device | |
| * | |
| * @v block_io Block I/O protocol | |
| * @v media Media identifier | |
| * @v lba Starting LBA | |
| * @v len Size of buffer | |
| * @v data Data buffer | |
| * @ret efirc EFI status code | |
| */ | |
| static EFI_STATUS EFIAPI | |
| efi_block_io_write ( EFI_BLOCK_IO_PROTOCOL *block_io, UINT32 media __unused, | |
| EFI_LBA lba, UINTN len, VOID *data ) { | |
| struct efi_block_data *block = | |
| container_of ( block_io, struct efi_block_data, block_io ); | |
| struct san_device *sandev = block->sandev; | |
| int rc; | |
| DBGC2 ( sandev->drive, "EFIBLK %#02x write LBA %#08llx from " | |
| "%p+%#08zx\n", sandev->drive, lba, data, ( ( size_t ) len ) ); | |
| efi_snp_claim(); | |
| rc = efi_block_rw ( sandev, lba, data, len, sandev_write ); | |
| efi_snp_release(); | |
| return EFIRC ( rc ); | |
| } | |
| /** | |
| * Flush data to EFI block device | |
| * | |
| * @v block_io Block I/O protocol | |
| * @ret efirc EFI status code | |
| */ | |
| static EFI_STATUS EFIAPI | |
| efi_block_io_flush ( EFI_BLOCK_IO_PROTOCOL *block_io ) { | |
| struct efi_block_data *block = | |
| container_of ( block_io, struct efi_block_data, block_io ); | |
| struct san_device *sandev = block->sandev; | |
| DBGC2 ( sandev->drive, "EFIBLK %#02x flush\n", sandev->drive ); | |
| /* Nothing to do */ | |
| return 0; | |
| } | |
| /** | |
| * Connect all possible drivers to EFI block device | |
| * | |
| * @v drive Drive number | |
| * @v handle Block device handle | |
| */ | |
| static void efi_block_connect ( unsigned int drive, EFI_HANDLE handle ) { | |
| int rc; | |
| /* Try to connect all possible drivers to this block device */ | |
| if ( ( rc = efi_connect ( handle, NULL ) ) != 0 ) { | |
| DBGC ( drive, "EFIBLK %#02x could not connect drivers: %s\n", | |
| drive, strerror ( rc ) ); | |
| /* May not be an error; may already be connected */ | |
| } | |
| DBGC2 ( drive, "EFIBLK %#02x supports protocols:\n", drive ); | |
| DBGC2_EFI_PROTOCOLS ( drive, handle ); | |
| } | |
| /** | |
| * Hook EFI block device | |
| * | |
| * @v drive Drive number | |
| * @v uris List of URIs | |
| * @v count Number of URIs | |
| * @v flags Flags | |
| * @ret drive Drive number, or negative error | |
| */ | |
| static int efi_block_hook ( unsigned int drive, struct uri **uris, | |
| unsigned int count, unsigned int flags ) { | |
| EFI_BOOT_SERVICES *bs = efi_systab->BootServices; | |
| struct san_device *sandev; | |
| struct efi_block_data *block; | |
| int leak = 0; | |
| EFI_STATUS efirc; | |
| int rc; | |
| /* Sanity check */ | |
| if ( ! count ) { | |
| DBGC ( drive, "EFIBLK %#02x has no URIs\n", drive ); | |
| rc = -ENOTTY; | |
| goto err_no_uris; | |
| } | |
| /* Allocate and initialise structure */ | |
| sandev = alloc_sandev ( uris, count, sizeof ( *block ) ); | |
| if ( ! sandev ) { | |
| rc = -ENOMEM; | |
| goto err_alloc; | |
| } | |
| block = sandev->priv; | |
| block->sandev = sandev; | |
| block->media.MediaPresent = 1; | |
| block->media.LogicalBlocksPerPhysicalBlock = 1; | |
| block->block_io.Revision = EFI_BLOCK_IO_PROTOCOL_REVISION3; | |
| block->block_io.Media = &block->media; | |
| block->block_io.Reset = efi_block_io_reset; | |
| block->block_io.ReadBlocks = efi_block_io_read; | |
| block->block_io.WriteBlocks = efi_block_io_write; | |
| block->block_io.FlushBlocks = efi_block_io_flush; | |
| /* Register SAN device */ | |
| if ( ( rc = register_sandev ( sandev, drive, flags ) ) != 0 ) { | |
| DBGC ( drive, "EFIBLK %#02x could not register: %s\n", | |
| drive, strerror ( rc ) ); | |
| goto err_register; | |
| } | |
| /* Update media descriptor */ | |
| block->media.BlockSize = | |
| ( sandev->capacity.blksize << sandev->blksize_shift ); | |
| block->media.LastBlock = | |
| ( ( sandev->capacity.blocks >> sandev->blksize_shift ) - 1 ); | |
| /* Construct device path */ | |
| if ( ! sandev->active ) { | |
| rc = -ENODEV; | |
| DBGC ( drive, "EFIBLK %#02x not active after registration\n", | |
| drive ); | |
| goto err_active; | |
| } | |
| block->path = efi_describe ( &sandev->active->block ); | |
| if ( ! block->path ) { | |
| rc = -ENODEV; | |
| DBGC ( drive, "EFIBLK %#02x has no device path\n", drive ); | |
| goto err_describe; | |
| } | |
| DBGC2 ( drive, "EFIBLK %#02x has device path %s\n", | |
| drive, efi_devpath_text ( block->path ) ); | |
| /* Install protocols */ | |
| if ( ( efirc = bs->InstallMultipleProtocolInterfaces ( | |
| &block->handle, | |
| &efi_block_io_protocol_guid, &block->block_io, | |
| &efi_device_path_protocol_guid, block->path, | |
| NULL ) ) != 0 ) { | |
| rc = -EEFI ( efirc ); | |
| DBGC ( drive, "EFIBLK %#02x could not install protocols: %s\n", | |
| drive, strerror ( rc ) ); | |
| goto err_install; | |
| } | |
| DBGC ( drive, "EFIBLK %#02x installed as SAN drive %s\n", | |
| drive, efi_handle_name ( block->handle ) ); | |
| /* Connect all possible protocols */ | |
| efi_block_connect ( drive, block->handle ); | |
| return drive; | |
| if ( ( efirc = bs->UninstallMultipleProtocolInterfaces ( | |
| block->handle, | |
| &efi_block_io_protocol_guid, &block->block_io, | |
| &efi_device_path_protocol_guid, block->path, | |
| NULL ) ) != 0 ) { | |
| DBGC ( drive, "EFIBLK %#02x could not uninstall protocols: " | |
| "%s\n", drive, strerror ( -EEFI ( efirc ) ) ); | |
| leak = 1; | |
| } | |
| efi_nullify_block ( &block->block_io ); | |
| err_install: | |
| if ( ! leak ) { | |
| free ( block->path ); | |
| block->path = NULL; | |
| } | |
| err_describe: | |
| err_active: | |
| unregister_sandev ( sandev ); | |
| err_register: | |
| if ( ! leak ) | |
| sandev_put ( sandev ); | |
| err_alloc: | |
| err_no_uris: | |
| if ( leak ) | |
| DBGC ( drive, "EFIBLK %#02x nullified and leaked\n", drive ); | |
| return rc; | |
| } | |
| /** | |
| * Unhook EFI block device | |
| * | |
| * @v drive Drive number | |
| */ | |
| static void efi_block_unhook ( unsigned int drive ) { | |
| EFI_BOOT_SERVICES *bs = efi_systab->BootServices; | |
| struct san_device *sandev; | |
| struct efi_block_data *block; | |
| int leak = efi_shutdown_in_progress; | |
| EFI_STATUS efirc; | |
| /* Find SAN device */ | |
| sandev = sandev_find ( drive ); | |
| if ( ! sandev ) { | |
| DBGC ( drive, "EFIBLK %#02x is not a SAN drive\n", drive ); | |
| return; | |
| } | |
| block = sandev->priv; | |
| /* Uninstall protocols */ | |
| if ( ( ! efi_shutdown_in_progress ) && | |
| ( ( efirc = bs->UninstallMultipleProtocolInterfaces ( | |
| block->handle, | |
| &efi_block_io_protocol_guid, &block->block_io, | |
| &efi_device_path_protocol_guid, block->path, | |
| NULL ) ) != 0 ) ) { | |
| DBGC ( drive, "EFIBLK %#02x could not uninstall protocols: " | |
| "%s\n", drive, strerror ( -EEFI ( efirc ) ) ); | |
| leak = 1; | |
| } | |
| efi_nullify_block ( &block->block_io ); | |
| /* Free device path */ | |
| if ( ! leak ) { | |
| free ( block->path ); | |
| block->path = NULL; | |
| } | |
| /* Unregister SAN device */ | |
| unregister_sandev ( sandev ); | |
| /* Drop reference to drive */ | |
| if ( ! leak ) | |
| sandev_put ( sandev ); | |
| /* Report leakage, if applicable */ | |
| if ( leak && ( ! efi_shutdown_in_progress ) ) | |
| DBGC ( drive, "EFIBLK %#02x nullified and leaked\n", drive ); | |
| } | |
| /** An installed ACPI table */ | |
| struct efi_acpi_table { | |
| /** List of installed tables */ | |
| struct list_head list; | |
| /** Table key */ | |
| UINTN key; | |
| }; | |
| /** List of installed ACPI tables */ | |
| static LIST_HEAD ( efi_acpi_tables ); | |
| /** | |
| * Install ACPI table | |
| * | |
| * @v hdr ACPI description header | |
| * @ret rc Return status code | |
| */ | |
| static int efi_block_install ( struct acpi_header *hdr ) { | |
| size_t len = le32_to_cpu ( hdr->length ); | |
| struct efi_acpi_table *installed; | |
| EFI_STATUS efirc; | |
| int rc; | |
| /* Allocate installed table record */ | |
| installed = zalloc ( sizeof ( *installed ) ); | |
| if ( ! installed ) { | |
| rc = -ENOMEM; | |
| goto err_alloc; | |
| } | |
| /* Fill in common parameters */ | |
| strncpy ( hdr->oem_id, "FENSYS", sizeof ( hdr->oem_id ) ); | |
| strncpy ( hdr->oem_table_id, "iPXE", sizeof ( hdr->oem_table_id ) ); | |
| /* Fix up ACPI checksum */ | |
| acpi_fix_checksum ( hdr ); | |
| /* Install table */ | |
| if ( ( efirc = acpi->InstallAcpiTable ( acpi, hdr, len, | |
| &installed->key ) ) != 0 ){ | |
| rc = -EEFI ( efirc ); | |
| DBGC ( acpi, "EFIBLK could not install %s: %s\n", | |
| acpi_name ( hdr->signature ), strerror ( rc ) ); | |
| DBGC2_HDA ( acpi, 0, hdr, len ); | |
| goto err_install; | |
| } | |
| /* Add to list of installed tables */ | |
| list_add_tail ( &installed->list, &efi_acpi_tables ); | |
| DBGC ( acpi, "EFIBLK installed %s as ACPI table %#lx\n", | |
| acpi_name ( hdr->signature ), | |
| ( ( unsigned long ) installed->key ) ); | |
| DBGC2_HDA ( acpi, 0, hdr, len ); | |
| return 0; | |
| list_del ( &installed->list ); | |
| err_install: | |
| free ( installed ); | |
| err_alloc: | |
| return rc; | |
| } | |
| /** | |
| * Describe EFI block devices | |
| * | |
| * @ret rc Return status code | |
| */ | |
| static int efi_block_describe ( void ) { | |
| struct efi_acpi_table *installed; | |
| struct efi_acpi_table *tmp; | |
| UINTN key; | |
| EFI_STATUS efirc; | |
| int rc; | |
| /* Sanity check */ | |
| if ( ! acpi ) { | |
| DBG ( "EFIBLK has no ACPI table protocol\n" ); | |
| return -ENOTSUP; | |
| } | |
| /* Uninstall any existing ACPI tables */ | |
| list_for_each_entry_safe ( installed, tmp, &efi_acpi_tables, list ) { | |
| key = installed->key; | |
| if ( ( efirc = acpi->UninstallAcpiTable ( acpi, key ) ) != 0 ) { | |
| rc = -EEFI ( efirc ); | |
| DBGC ( acpi, "EFIBLK could not uninstall ACPI table " | |
| "%#lx: %s\n", ( ( unsigned long ) key ), | |
| strerror ( rc ) ); | |
| /* Continue anyway */ | |
| } | |
| list_del ( &installed->list ); | |
| free ( installed ); | |
| } | |
| /* Install ACPI tables */ | |
| if ( ( rc = acpi_install ( efi_block_install ) ) != 0 ) { | |
| DBGC ( acpi, "EFIBLK could not install ACPI tables: %s\n", | |
| strerror ( rc ) ); | |
| return rc; | |
| } | |
| return 0; | |
| } | |
| /** | |
| * Open root directory within a filesystem | |
| * | |
| * @v drive Drive number | |
| * @v handle Filesystem handle | |
| * @v root Root directory file to fill in | |
| * @ret rc Return status code | |
| */ | |
| static int efi_block_root ( unsigned int drive, EFI_HANDLE handle, | |
| EFI_FILE_PROTOCOL **root ) { | |
| EFI_SIMPLE_FILE_SYSTEM_PROTOCOL *fs; | |
| EFI_STATUS efirc; | |
| int rc; | |
| /* Open filesystem protocol */ | |
| if ( ( rc = efi_open ( handle, &efi_simple_file_system_protocol_guid, | |
| &fs ) ) != 0 ) { | |
| DBGC ( drive, "EFIBLK %#02x could not open %s filesystem: %s\n", | |
| drive, efi_handle_name ( handle ), strerror ( rc ) ); | |
| return rc; | |
| } | |
| /* Open root volume */ | |
| if ( ( efirc = fs->OpenVolume ( fs, root ) ) != 0 ) { | |
| rc = -EEFI ( efirc ); | |
| DBGC ( drive, "EFIBLK %#02x could not open %s root: %s\n", | |
| drive, efi_handle_name ( handle ), strerror ( rc ) ); | |
| return rc; | |
| } | |
| return 0; | |
| } | |
| /** | |
| * Check for existence of a file within a filesystem | |
| * | |
| * @v drive Drive number | |
| * @v handle Filesystem handle | |
| * @v root Root directory | |
| * @v filename Filename (or NULL to use default) | |
| * @ret rc Return status code | |
| */ | |
| static int efi_block_filename ( unsigned int drive, EFI_HANDLE handle, | |
| EFI_FILE_PROTOCOL *root, | |
| const char *filename ) { | |
| CHAR16 tmp[ filename ? ( strlen ( filename ) + 1 /* wNUL */ ) : 0 ]; | |
| CHAR16 *wname; | |
| EFI_FILE_PROTOCOL *file; | |
| EFI_STATUS efirc; | |
| int rc; | |
| /* Construct filename */ | |
| if ( filename ) { | |
| efi_snprintf ( tmp, sizeof ( tmp ), "%s", filename ); | |
| wname = tmp; | |
| } else { | |
| wname = efi_block_boot_filename; | |
| } | |
| /* Try opening file */ | |
| if ( ( efirc = root->Open ( root, &file, wname, | |
| EFI_FILE_MODE_READ, 0 ) ) != 0 ) { | |
| rc = -EEFI ( efirc ); | |
| DBGC ( drive, "EFIBLK %#02x could not open %s/%ls: %s\n", | |
| drive, efi_handle_name ( handle ), wname, | |
| strerror ( rc ) ); | |
| goto err_file; | |
| } | |
| /* Success */ | |
| rc = 0; | |
| file->Close ( file ); | |
| err_file: | |
| return rc; | |
| } | |
| /** | |
| * Check for EFI block device filesystem label | |
| * | |
| * @v drive Drive number | |
| * @v root Root directory | |
| * @v label Volume label | |
| * @ret rc Return status code | |
| */ | |
| static int efi_block_label ( unsigned int drive, EFI_FILE_PROTOCOL *root, | |
| const char *label ) { | |
| EFI_FILE_SYSTEM_INFO *info; | |
| UINTN size; | |
| char *actual; | |
| EFI_STATUS efirc; | |
| int rc; | |
| /* 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 = -ENOMEM; | |
| goto err_alloc_info; | |
| } | |
| /* Get file system information */ | |
| if ( ( efirc = root->GetInfo ( root, &efi_file_system_info_id, &size, | |
| info ) ) != 0 ) { | |
| rc = -EEFI ( efirc ); | |
| DBGC ( drive, "EFIBLK %#02x could not get filesystem info: " | |
| "%s\n", drive, strerror ( rc ) ); | |
| goto err_get_info; | |
| } | |
| /* Construct volume label for comparison */ | |
| if ( asprintf ( &actual, "%ls", info->VolumeLabel ) < 0 ) { | |
| rc = -ENOMEM; | |
| goto err_alloc_label; | |
| } | |
| /* Compare volume label */ | |
| if ( strcasecmp ( label, actual ) != 0 ) { | |
| DBGC ( drive, "EFIBLK %#02x has wrong label \"%s\"\n", | |
| drive, actual ); | |
| rc = -ENOENT; | |
| goto err_compare; | |
| } | |
| /* Success */ | |
| rc = 0; | |
| err_compare: | |
| free ( actual ); | |
| err_alloc_label: | |
| err_get_info: | |
| free ( info ); | |
| err_alloc_info: | |
| return rc; | |
| } | |
| /** | |
| * Check EFI block device filesystem match | |
| * | |
| * @v drive Drive number | |
| * @v handle Filesystem handle | |
| * @v path Block device path | |
| * @v config Boot configuration parameters | |
| * @v fspath Filesystem device path to fill in | |
| * @ret rc Return status code | |
| */ | |
| static int efi_block_match ( unsigned int drive, EFI_HANDLE handle, | |
| EFI_DEVICE_PATH_PROTOCOL *path, | |
| struct san_boot_config *config, | |
| EFI_DEVICE_PATH_PROTOCOL **fspath ) { | |
| EFI_FILE *root; | |
| union uuid guid; | |
| int rc; | |
| /* Identify device path */ | |
| if ( ( rc = efi_open ( handle, &efi_device_path_protocol_guid, | |
| fspath ) ) != 0 ) { | |
| DBGC ( drive, "EFIBLK %#02x could not open %s device path: " | |
| "%s\n", drive, efi_handle_name ( handle ), | |
| strerror ( rc ) ); | |
| goto err_open; | |
| } | |
| /* Check if filesystem is a child of this block device */ | |
| if ( memcmp ( *fspath, path, efi_path_len ( path ) ) != 0 ) { | |
| /* Not a child device */ | |
| rc = -ENOTTY; | |
| DBGC2 ( drive, "EFIBLK %#02x is not parent of %s\n", | |
| drive, efi_handle_name ( handle ) ); | |
| goto err_not_child; | |
| } | |
| DBGC ( drive, "EFIBLK %#02x contains filesystem %s\n", | |
| drive, efi_devpath_text ( *fspath ) ); | |
| /* Check if filesystem matches GUID, if applicable */ | |
| if ( config->uuid ) { | |
| if ( ( rc = efi_path_guid ( *fspath, &guid ) ) != 0 ) { | |
| DBGC ( drive, "EFIBLK %#02x could not determine GUID: " | |
| "%s\n", drive, strerror ( rc ) ); | |
| goto err_no_guid; | |
| } | |
| if ( memcmp ( config->uuid, &guid, sizeof ( guid ) ) != 0 ) { | |
| DBGC ( drive, "EFIBLK %#02x has wrong GUID %s\n", | |
| drive, uuid_ntoa ( &guid ) ); | |
| rc = -ENOENT; | |
| goto err_wrong_guid; | |
| } | |
| } | |
| /* Open root directory */ | |
| if ( ( rc = efi_block_root ( drive, handle, &root ) ) != 0 ) | |
| goto err_root; | |
| /* Check if filesystem contains boot filename */ | |
| if ( ( rc = efi_block_filename ( drive, handle, root, | |
| config->filename ) ) != 0 ) { | |
| goto err_filename; | |
| } | |
| /* Check if filesystem contains additional filename, if applicable */ | |
| if ( config->extra && | |
| ( ( rc = efi_block_filename ( drive, handle, root, | |
| config->extra ) ) != 0 ) ) { | |
| goto err_extra; | |
| } | |
| /* Check volume label, if applicable */ | |
| if ( config->label && | |
| ( ( rc = efi_block_label ( drive, root, | |
| config->label ) ) != 0 ) ) { | |
| goto err_label; | |
| } | |
| /* Success */ | |
| rc = 0; | |
| err_label: | |
| err_extra: | |
| err_filename: | |
| root->Close ( root ); | |
| err_root: | |
| err_wrong_guid: | |
| err_no_guid: | |
| err_not_child: | |
| err_open: | |
| return rc; | |
| } | |
| /** | |
| * Scan EFI block device for a matching filesystem | |
| * | |
| * @v drive Drive number | |
| * @v handle Block device handle | |
| * @v config Boot configuration parameters | |
| * @v fspath Filesystem device path to fill in | |
| * @ret rc Return status code | |
| */ | |
| static int efi_block_scan ( unsigned int drive, EFI_HANDLE handle, | |
| struct san_boot_config *config, | |
| EFI_DEVICE_PATH_PROTOCOL **fspath ) { | |
| EFI_BOOT_SERVICES *bs = efi_systab->BootServices; | |
| EFI_DEVICE_PATH_PROTOCOL *path; | |
| EFI_HANDLE *handles; | |
| UINTN count; | |
| unsigned int i; | |
| EFI_STATUS efirc; | |
| int rc; | |
| /* Connect up possible file system drivers */ | |
| efi_block_connect ( drive, handle ); | |
| /* Identify device path */ | |
| if ( ( rc = efi_open ( handle, &efi_device_path_protocol_guid, | |
| &path ) ) != 0 ) { | |
| DBGC ( drive, "EFIBLK %#02x could not open device path: %s\n", | |
| drive, strerror ( rc ) ); | |
| goto err_open; | |
| } | |
| /* Locate all Simple File System protocol handles */ | |
| if ( ( efirc = bs->LocateHandleBuffer ( | |
| ByProtocol, &efi_simple_file_system_protocol_guid, | |
| NULL, &count, &handles ) ) != 0 ) { | |
| rc = -EEFI ( efirc ); | |
| DBGC ( drive, "EFIBLK %#02x cannot locate file systems: %s\n", | |
| drive, strerror ( rc ) ); | |
| goto err_locate; | |
| } | |
| /* Scan for a matching filesystem */ | |
| rc = -ENOENT; | |
| for ( i = 0 ; i < count ; i++ ) { | |
| /* Check for a matching filesystem */ | |
| if ( ( rc = efi_block_match ( drive, handles[i], path, | |
| config, fspath ) ) != 0 ) | |
| continue; | |
| break; | |
| } | |
| bs->FreePool ( handles ); | |
| err_locate: | |
| err_open: | |
| return rc; | |
| } | |
| /** | |
| * Boot from EFI block device filesystem boot image | |
| * | |
| * @v drive Drive number | |
| * @v fspath Filesystem device path | |
| * @v filename Filename (or NULL to use default) | |
| * @ret rc Return status code | |
| */ | |
| static int efi_block_exec ( unsigned int drive, | |
| EFI_DEVICE_PATH_PROTOCOL *fspath, | |
| const char *filename ) { | |
| EFI_BOOT_SERVICES *bs = efi_systab->BootServices; | |
| EFI_DEVICE_PATH_PROTOCOL *path; | |
| FILEPATH_DEVICE_PATH *filepath; | |
| EFI_DEVICE_PATH_PROTOCOL *end; | |
| EFI_HANDLE image; | |
| size_t fspath_len; | |
| size_t filepath_len; | |
| size_t path_len; | |
| EFI_STATUS efirc; | |
| int rc; | |
| /* Construct device path for boot image */ | |
| end = efi_path_end ( fspath ); | |
| fspath_len = ( ( ( void * ) end ) - ( ( void * ) fspath ) ); | |
| filepath_len = ( SIZE_OF_FILEPATH_DEVICE_PATH + | |
| ( filename ? | |
| ( ( strlen ( filename ) + 1 /* NUL */ ) * | |
| sizeof ( filepath->PathName[0] ) ) : | |
| sizeof ( efi_block_boot_filename ) ) ); | |
| path_len = ( fspath_len + filepath_len + sizeof ( *end ) ); | |
| path = zalloc ( path_len ); | |
| if ( ! path ) { | |
| rc = -ENOMEM; | |
| goto err_alloc; | |
| } | |
| memcpy ( path, fspath, fspath_len ); | |
| filepath = ( ( ( void * ) path ) + fspath_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_block_boot_filename, | |
| sizeof ( efi_block_boot_filename ) ); | |
| } | |
| end = ( ( ( void * ) filepath ) + filepath_len ); | |
| efi_path_terminate ( end ); | |
| DBGC ( drive, "EFIBLK %#02x trying to load %s\n", | |
| drive, efi_devpath_text ( path ) ); | |
| /* Load image */ | |
| image = NULL; | |
| if ( ( efirc = bs->LoadImage ( FALSE, efi_image_handle, path, NULL, 0, | |
| &image ) ) != 0 ) { | |
| rc = -EEFI ( efirc ); | |
| DBGC ( drive, "EFIBLK %#02x could not load: %s\n", | |
| drive, strerror ( rc ) ); | |
| if ( efirc == EFI_SECURITY_VIOLATION ) { | |
| goto err_load_security_violation; | |
| } else { | |
| goto err_load; | |
| } | |
| } | |
| /* Wrap calls made by the loaded image (for debugging) */ | |
| efi_wrap_image ( image ); | |
| /* Start image */ | |
| efirc = bs->StartImage ( image, NULL, NULL ); | |
| rc = ( efirc ? -EEFI ( efirc ) : 0 ); | |
| DBGC ( drive, "EFIBLK %#02x boot image returned: %s\n", | |
| drive, strerror ( rc ) ); | |
| /* Remove wrapper */ | |
| efi_unwrap(); | |
| err_load_security_violation: | |
| bs->UnloadImage ( image ); | |
| err_load: | |
| free ( path ); | |
| err_alloc: | |
| return rc; | |
| } | |
| /** | |
| * Check that EFI block device is eligible for a local virtual drive number | |
| * | |
| * @v handle Block device handle | |
| * @ret rc Return status code | |
| * | |
| * We assign virtual drive numbers for local (non-SAN) EFI block | |
| * devices that represent complete disks, to provide roughly | |
| * equivalent functionality to BIOS drive numbers. | |
| */ | |
| static int efi_block_local ( EFI_HANDLE handle ) { | |
| struct san_device *sandev; | |
| struct efi_block_data *block; | |
| EFI_BLOCK_IO_PROTOCOL *blockio; | |
| int rc; | |
| /* Check if handle belongs to a SAN device */ | |
| for_each_sandev ( sandev ) { | |
| block = sandev->priv; | |
| if ( handle == block->handle ) | |
| return -ENOTTY; | |
| } | |
| /* Open block I/O protocol */ | |
| if ( ( rc = efi_open ( handle, &efi_block_io_protocol_guid, | |
| &blockio ) ) != 0 ) { | |
| DBGC ( handle, "EFIBLK %s could not open block I/O: %s\n", | |
| efi_handle_name ( handle ), strerror ( rc ) ); | |
| return rc; | |
| } | |
| /* Do not assign drive numbers for partitions */ | |
| if ( blockio->Media->LogicalPartition ) { | |
| DBGC2 ( handle, "EFLBLK %s is a partition\n", | |
| efi_handle_name ( handle ) ); | |
| return -ENOTTY; | |
| } | |
| return 0; | |
| } | |
| /** | |
| * Boot from EFI block device | |
| * | |
| * @v drive Drive number | |
| * @v config Boot configuration parameters | |
| * @ret rc Return status code | |
| */ | |
| static int efi_block_boot ( unsigned int drive, | |
| struct san_boot_config *config ) { | |
| EFI_BOOT_SERVICES *bs = efi_systab->BootServices; | |
| EFI_DEVICE_PATH_PROTOCOL *fspath = NULL; | |
| EFI_HANDLE *handles; | |
| EFI_HANDLE handle; | |
| UINTN count; | |
| struct san_device *sandev; | |
| struct efi_block_data *block; | |
| unsigned int vdrive; | |
| unsigned int index; | |
| EFI_STATUS efirc; | |
| int rc; | |
| /* Ensure that any local drives are connected */ | |
| efi_driver_reconnect_all(); | |
| /* Release SNP devices */ | |
| efi_snp_release(); | |
| /* Locate all block I/O protocol handles */ | |
| if ( ( efirc = bs->LocateHandleBuffer ( ByProtocol, | |
| &efi_block_io_protocol_guid, | |
| NULL, &count, | |
| &handles ) ) != 0 ) { | |
| rc = -EEFI ( efirc ); | |
| DBGC ( drive, "EFIBLK %#02x cannot locate block I/O: %s\n", | |
| drive, strerror ( rc ) ); | |
| goto err_locate_block_io; | |
| } | |
| /* Try booting from the first matching block device, if any */ | |
| rc = -ENOENT; | |
| for ( vdrive = 0, index = 0 ; ; vdrive++ ) { | |
| /* Identify next drive number and block I/O handle */ | |
| if ( ( sandev = sandev_next ( vdrive ) ) && | |
| ( ( sandev->drive == vdrive ) || | |
| ( sandev->drive <= SAN_DEFAULT_DRIVE ) || | |
| ( index >= count ) ) ) { | |
| /* There is a SAN drive that either: | |
| * | |
| * a) has the current virtual drive number, or | |
| * b) has a drive number below SAN_DEFAULT_DRIVE, or | |
| * c) has a drive number higher than any local drive | |
| * | |
| * Use this SAN drive, since the explicit SAN | |
| * drive numbering takes precedence over the | |
| * implicit local drive numbering. | |
| */ | |
| block = sandev->priv; | |
| handle = block->handle; | |
| /* Use SAN drive's explicit drive number */ | |
| vdrive = sandev->drive; | |
| DBGC ( vdrive, "EFIBLK %#02x is SAN drive %s\n", | |
| vdrive, efi_handle_name ( handle ) ); | |
| } else if ( index < count ) { | |
| /* There is no SAN drive meeting any of the | |
| * above criteria. Try the next block I/O | |
| * handle. | |
| */ | |
| handle = handles[index++]; | |
| /* Check if this handle is eligible to be | |
| * given a local virtual drive number. | |
| * | |
| * Do not record this as the overall error | |
| * status, since it is not an interesting | |
| * error. | |
| */ | |
| if ( efi_block_local ( handle ) != 0 ) { | |
| /* Do not consume virtual drive number */ | |
| vdrive--; | |
| continue; | |
| } | |
| /* Use the current virtual drive number, with | |
| * a minimum of SAN_DEFAULT_DRIVE to match | |
| * typical BIOS drive numbering. | |
| */ | |
| if ( vdrive < SAN_DEFAULT_DRIVE ) | |
| vdrive = SAN_DEFAULT_DRIVE; | |
| DBGC ( vdrive, "EFIBLK %#02x is local drive %s\n", | |
| vdrive, efi_handle_name ( handle ) ); | |
| } else { | |
| /* No more SAN or local drives */ | |
| break; | |
| } | |
| /* Skip non-matching drives */ | |
| if ( drive && ( drive != vdrive ) ) | |
| continue; | |
| DBGC ( vdrive, "EFIBLK %#02x attempting to boot\n", vdrive ); | |
| /* Scan for a matching filesystem within this drive */ | |
| if ( ( rc = efi_block_scan ( vdrive, handle, config, | |
| &fspath ) ) != 0 ) { | |
| continue; | |
| } | |
| /* Attempt to boot from the matched filesystem */ | |
| rc = efi_block_exec ( vdrive, fspath, config->filename ); | |
| break; | |
| } | |
| bs->FreePool ( handles ); | |
| err_locate_block_io: | |
| efi_snp_claim(); | |
| return rc; | |
| } | |
| PROVIDE_SANBOOT ( efi, san_hook, efi_block_hook ); | |
| PROVIDE_SANBOOT ( efi, san_unhook, efi_block_unhook ); | |
| PROVIDE_SANBOOT ( efi, san_describe, efi_block_describe ); | |
| PROVIDE_SANBOOT ( efi, san_boot, efi_block_boot ); |