| /* |
| * 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 |
| * |
| * 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_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, |
| userptr_t 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, |
| virt_to_user ( 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 ) { |
| EFI_BOOT_SERVICES *bs = efi_systab->BootServices; |
| EFI_STATUS efirc; |
| int rc; |
| |
| /* Try to connect all possible drivers to this block device */ |
| if ( ( efirc = bs->ConnectController ( handle, NULL, NULL, |
| TRUE ) ) != 0 ) { |
| rc = -EEFI ( efirc ); |
| 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_BOOT_SERVICES *bs = efi_systab->BootServices; |
| EFI_GUID *protocol = &efi_simple_file_system_protocol_guid; |
| union { |
| EFI_SIMPLE_FILE_SYSTEM_PROTOCOL *fs; |
| void *interface; |
| } u; |
| EFI_STATUS efirc; |
| int rc; |
| |
| /* Open filesystem protocol */ |
| if ( ( efirc = bs->OpenProtocol ( handle, protocol, &u.interface, |
| efi_image_handle, handle, |
| EFI_OPEN_PROTOCOL_GET_PROTOCOL ))!=0){ |
| rc = -EEFI ( efirc ); |
| DBGC ( drive, "EFIBLK %#02x could not open %s filesystem: %s\n", |
| drive, efi_handle_name ( handle ), strerror ( rc ) ); |
| goto err_open; |
| } |
| |
| /* Open root volume */ |
| if ( ( efirc = u.fs->OpenVolume ( u.fs, root ) ) != 0 ) { |
| rc = -EEFI ( efirc ); |
| DBGC ( drive, "EFIBLK %#02x could not open %s root: %s\n", |
| drive, efi_handle_name ( handle ), strerror ( rc ) ); |
| goto err_volume; |
| } |
| |
| /* Success */ |
| rc = 0; |
| |
| err_volume: |
| bs->CloseProtocol ( handle, protocol, efi_image_handle, handle ); |
| err_open: |
| return rc; |
| } |
| |
| /** |
| * 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_BOOT_SERVICES *bs = efi_systab->BootServices; |
| EFI_GUID *protocol = &efi_device_path_protocol_guid; |
| union { |
| EFI_DEVICE_PATH_PROTOCOL *path; |
| void *interface; |
| } u; |
| EFI_FILE *root; |
| union uuid guid; |
| EFI_STATUS efirc; |
| int rc; |
| |
| /* Identify device path */ |
| if ( ( efirc = bs->OpenProtocol ( handle, protocol, &u.interface, |
| efi_image_handle, handle, |
| EFI_OPEN_PROTOCOL_GET_PROTOCOL ))!=0){ |
| rc = -EEFI ( efirc ); |
| DBGC ( drive, "EFIBLK %#02x could not open %s device path: " |
| "%s\n", drive, efi_handle_name ( handle ), |
| strerror ( rc ) ); |
| goto err_open; |
| } |
| *fspath = u.path; |
| |
| /* Check if filesystem is a child of this block device */ |
| if ( memcmp ( u.path, 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 ( u.path ) ); |
| |
| /* Check if filesystem matches GUID, if applicable */ |
| if ( config->uuid ) { |
| if ( ( rc = efi_path_guid ( u.path, &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: |
| bs->CloseProtocol ( handle, protocol, efi_image_handle, handle ); |
| 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_GUID *protocol = &efi_device_path_protocol_guid; |
| union { |
| EFI_DEVICE_PATH_PROTOCOL *path; |
| void *interface; |
| } u; |
| 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 ( ( efirc = bs->OpenProtocol ( handle, protocol, &u.interface, |
| efi_image_handle, handle, |
| EFI_OPEN_PROTOCOL_GET_PROTOCOL ))!=0){ |
| rc = -EEFI ( efirc ); |
| 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], u.path, |
| config, fspath ) ) != 0 ) |
| continue; |
| |
| break; |
| } |
| |
| bs->FreePool ( handles ); |
| err_locate: |
| bs->CloseProtocol ( handle, protocol, efi_image_handle, handle ); |
| 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; |
| } |
| } |
| |
| /* 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 ) ); |
| |
| 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 ) { |
| EFI_BOOT_SERVICES *bs = efi_systab->BootServices; |
| EFI_GUID *protocol = &efi_block_io_protocol_guid; |
| struct san_device *sandev; |
| struct efi_block_data *block; |
| union { |
| EFI_BLOCK_IO_PROTOCOL *blockio; |
| void *interface; |
| } u; |
| EFI_STATUS efirc; |
| int rc; |
| |
| /* Check if handle belongs to a SAN device */ |
| for_each_sandev ( sandev ) { |
| block = sandev->priv; |
| if ( handle == block->handle ) { |
| rc = -ENOTTY; |
| goto err_sandev; |
| } |
| } |
| |
| /* Open block I/O protocol */ |
| if ( ( efirc = bs->OpenProtocol ( handle, protocol, &u.interface, |
| efi_image_handle, handle, |
| EFI_OPEN_PROTOCOL_GET_PROTOCOL ))!=0){ |
| rc = -EEFI ( efirc ); |
| DBGC ( handle, "EFIBLK %s could not open block I/O: %s\n", |
| efi_handle_name ( handle ), strerror ( rc ) ); |
| goto err_open; |
| } |
| |
| /* Do not assign drive numbers for partitions */ |
| if ( u.blockio->Media->LogicalPartition ) { |
| rc = -ENOTTY; |
| DBGC2 ( handle, "EFLBLK %s is a partition\n", |
| efi_handle_name ( handle ) ); |
| goto err_partition; |
| } |
| |
| /* Success */ |
| rc = 0; |
| |
| err_partition: |
| bs->CloseProtocol ( handle, protocol, efi_image_handle, handle ); |
| err_open: |
| err_sandev: |
| return rc; |
| } |
| |
| /** |
| * 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 ); |