X-Git-Url: https://gerrit.opnfv.org/gerrit/gitweb?a=blobdiff_plain;f=qemu%2Froms%2Fipxe%2Fsrc%2Finterface%2Fefi%2Fefi_file.c;fp=qemu%2Froms%2Fipxe%2Fsrc%2Finterface%2Fefi%2Fefi_file.c;h=2ef3c57347cd3df3aa6b364d62fdaa3563049fca;hb=e44e3482bdb4d0ebde2d8b41830ac2cdb07948fb;hp=0000000000000000000000000000000000000000;hpb=9ca8dbcc65cfc63d6f5ef3312a33184e1d726e00;p=kvmfornfv.git diff --git a/qemu/roms/ipxe/src/interface/efi/efi_file.c b/qemu/roms/ipxe/src/interface/efi/efi_file.c new file mode 100644 index 000000000..2ef3c5734 --- /dev/null +++ b/qemu/roms/ipxe/src/interface/efi/efi_file.c @@ -0,0 +1,714 @@ +/* + * Copyright (C) 2013 Michael Brown . + * + * 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. + */ + +FILE_LICENCE ( GPL2_OR_LATER ); + +/** + * @file + * + * EFI file protocols + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/** EFI file information GUID */ +static EFI_GUID efi_file_info_id = EFI_FILE_INFO_ID; + +/** EFI file system information GUID */ +static EFI_GUID efi_file_system_info_id = EFI_FILE_SYSTEM_INFO_ID; + +/** EFI media ID */ +#define EFI_MEDIA_ID_MAGIC 0x69505845 + +/** An image exposed as an EFI file */ +struct efi_file { + /** EFI file protocol */ + EFI_FILE_PROTOCOL file; + /** Image */ + struct image *image; + /** Current file position */ + size_t pos; +}; + +static struct efi_file efi_file_root; + +/** + * Get EFI file name (for debugging) + * + * @v file EFI file + * @ret name Name + */ +static const char * efi_file_name ( struct efi_file *file ) { + + return ( file->image ? file->image->name : "" ); +} + +/** + * Find EFI file image + * + * @v wname Filename + * @ret image Image, or NULL + */ +static struct image * efi_file_find ( const CHAR16 *wname ) { + char name[ wcslen ( wname ) + 1 /* NUL */ ]; + struct image *image; + + /* Find image */ + snprintf ( name, sizeof ( name ), "%ls", wname ); + list_for_each_entry ( image, &images, list ) { + if ( strcasecmp ( image->name, name ) == 0 ) + return image; + } + + return NULL; + +} + +/** + * Open file + * + * @v this EFI file + * @ret new New EFI file + * @v wname Filename + * @v mode File mode + * @v attributes File attributes (for newly-created files) + * @ret efirc EFI status code + */ +static EFI_STATUS EFIAPI +efi_file_open ( EFI_FILE_PROTOCOL *this, EFI_FILE_PROTOCOL **new, + CHAR16 *wname, UINT64 mode __unused, + UINT64 attributes __unused ) { + struct efi_file *file = container_of ( this, struct efi_file, file ); + struct efi_file *new_file; + struct image *image; + + /* Initial '\' indicates opening from the root directory */ + while ( *wname == L'\\' ) { + file = &efi_file_root; + wname++; + } + + /* Allow root directory itself to be opened */ + if ( ( wname[0] == L'\0' ) || ( wname[0] == L'.' ) ) { + *new = &efi_file_root.file; + return 0; + } + + /* Fail unless opening from the root */ + if ( file->image ) { + DBGC ( file, "EFIFILE %s is not a directory\n", + efi_file_name ( file ) ); + return EFI_NOT_FOUND; + } + + /* Identify image */ + image = efi_file_find ( wname ); + if ( ! image ) { + DBGC ( file, "EFIFILE \"%ls\" does not exist\n", wname ); + return EFI_NOT_FOUND; + } + + /* Fail unless opening read-only */ + if ( mode != EFI_FILE_MODE_READ ) { + DBGC ( file, "EFIFILE %s cannot be opened in mode %#08llx\n", + image->name, mode ); + return EFI_WRITE_PROTECTED; + } + + /* Allocate and initialise file */ + new_file = zalloc ( sizeof ( *new_file ) ); + memcpy ( &new_file->file, &efi_file_root.file, + sizeof ( new_file->file ) ); + new_file->image = image_get ( image ); + *new = &new_file->file; + DBGC ( new_file, "EFIFILE %s opened\n", efi_file_name ( new_file ) ); + + return 0; +} + +/** + * Close file + * + * @v this EFI file + * @ret efirc EFI status code + */ +static EFI_STATUS EFIAPI efi_file_close ( EFI_FILE_PROTOCOL *this ) { + struct efi_file *file = container_of ( this, struct efi_file, file ); + + /* Do nothing if this is the root */ + if ( ! file->image ) + return 0; + + /* Close file */ + DBGC ( file, "EFIFILE %s closed\n", efi_file_name ( file ) ); + image_put ( file->image ); + free ( file ); + + return 0; +} + +/** + * Close and delete file + * + * @v this EFI file + * @ret efirc EFI status code + */ +static EFI_STATUS EFIAPI efi_file_delete ( EFI_FILE_PROTOCOL *this ) { + struct efi_file *file = container_of ( this, struct efi_file, file ); + + DBGC ( file, "EFIFILE %s cannot be deleted\n", efi_file_name ( file ) ); + + /* Close file */ + efi_file_close ( this ); + + /* Warn of failure to delete */ + return EFI_WARN_DELETE_FAILURE; +} + +/** + * Return variable-length data structure + * + * @v base Base data structure (starting with UINT64) + * @v base_len Length of base data structure + * @v name Name to append to base data structure + * @v len Length of data buffer + * @v data Data buffer + * @ret efirc EFI status code + */ +static EFI_STATUS efi_file_varlen ( UINT64 *base, size_t base_len, + const char *name, UINTN *len, VOID *data ) { + size_t name_len; + + /* Calculate structure length */ + name_len = strlen ( name ); + *base = ( base_len + ( name_len + 1 /* NUL */ ) * sizeof ( wchar_t ) ); + if ( *len < *base ) { + *len = *base; + return EFI_BUFFER_TOO_SMALL; + } + + /* Copy data to buffer */ + *len = *base; + memcpy ( data, base, base_len ); + efi_snprintf ( ( data + base_len ), ( name_len + 1 /* NUL */ ), + "%s", name ); + + return 0; +} + +/** + * Return file information structure + * + * @v image Image, or NULL for the root directory + * @v len Length of data buffer + * @v data Data buffer + * @ret efirc EFI status code + */ +static EFI_STATUS efi_file_info ( struct image *image, UINTN *len, + VOID *data ) { + EFI_FILE_INFO info; + const char *name; + + /* Populate file information */ + memset ( &info, 0, sizeof ( info ) ); + if ( image ) { + info.FileSize = image->len; + info.PhysicalSize = image->len; + info.Attribute = EFI_FILE_READ_ONLY; + name = image->name; + } else { + info.Attribute = ( EFI_FILE_READ_ONLY | EFI_FILE_DIRECTORY ); + name = ""; + } + + return efi_file_varlen ( &info.Size, SIZE_OF_EFI_FILE_INFO, name, + len, data ); +} + +/** + * Read directory entry + * + * @v file EFI file + * @v len Length to read + * @v data Data buffer + * @ret efirc EFI status code + */ +static EFI_STATUS efi_file_read_dir ( struct efi_file *file, UINTN *len, + VOID *data ) { + EFI_STATUS efirc; + struct image *image; + unsigned int index; + + /* Construct directory entry at current position */ + index = file->pos; + for_each_image ( image ) { + if ( index-- == 0 ) { + efirc = efi_file_info ( image, len, data ); + if ( efirc == 0 ) + file->pos++; + return efirc; + } + } + + /* No more entries */ + *len = 0; + return 0; +} + +/** + * Read from file + * + * @v this EFI file + * @v len Length to read + * @v data Data buffer + * @ret efirc EFI status code + */ +static EFI_STATUS EFIAPI efi_file_read ( EFI_FILE_PROTOCOL *this, + UINTN *len, VOID *data ) { + struct efi_file *file = container_of ( this, struct efi_file, file ); + size_t remaining; + + /* If this is the root directory, then construct a directory entry */ + if ( ! file->image ) + return efi_file_read_dir ( file, len, data ); + + /* Read from the file */ + remaining = ( file->image->len - file->pos ); + if ( *len > remaining ) + *len = remaining; + DBGC ( file, "EFIFILE %s read [%#08zx,%#08zx)\n", + efi_file_name ( file ), file->pos, + ( ( size_t ) ( file->pos + *len ) ) ); + copy_from_user ( data, file->image->data, file->pos, *len ); + file->pos += *len; + return 0; +} + +/** + * Write to file + * + * @v this EFI file + * @v len Length to write + * @v data Data buffer + * @ret efirc EFI status code + */ +static EFI_STATUS EFIAPI efi_file_write ( EFI_FILE_PROTOCOL *this, + UINTN *len, VOID *data __unused ) { + struct efi_file *file = container_of ( this, struct efi_file, file ); + + DBGC ( file, "EFIFILE %s cannot write [%#08zx, %#08zx)\n", + efi_file_name ( file ), file->pos, + ( ( size_t ) ( file->pos + *len ) ) ); + return EFI_WRITE_PROTECTED; +} + +/** + * Set file position + * + * @v this EFI file + * @v position New file position + * @ret efirc EFI status code + */ +static EFI_STATUS EFIAPI efi_file_set_position ( EFI_FILE_PROTOCOL *this, + UINT64 position ) { + struct efi_file *file = container_of ( this, struct efi_file, file ); + + /* If this is the root directory, reset to the start */ + if ( ! file->image ) { + DBGC ( file, "EFIFILE root directory rewound\n" ); + file->pos = 0; + return 0; + } + + /* Check for the magic end-of-file value */ + if ( position == 0xffffffffffffffffULL ) + position = file->image->len; + + /* Fail if we attempt to seek past the end of the file (since + * we do not support writes). + */ + if ( position > file->image->len ) { + DBGC ( file, "EFIFILE %s cannot seek to %#08llx of %#08zx\n", + efi_file_name ( file ), position, file->image->len ); + return EFI_UNSUPPORTED; + } + + /* Set position */ + file->pos = position; + DBGC ( file, "EFIFILE %s position set to %#08zx\n", + efi_file_name ( file ), file->pos ); + + return 0; +} + +/** + * Get file position + * + * @v this EFI file + * @ret position New file position + * @ret efirc EFI status code + */ +static EFI_STATUS EFIAPI efi_file_get_position ( EFI_FILE_PROTOCOL *this, + UINT64 *position ) { + struct efi_file *file = container_of ( this, struct efi_file, file ); + + *position = file->pos; + return 0; +} + +/** + * Get file information + * + * @v this EFI file + * @v type Type of information + * @v len Buffer size + * @v data Buffer + * @ret efirc EFI status code + */ +static EFI_STATUS EFIAPI efi_file_get_info ( EFI_FILE_PROTOCOL *this, + EFI_GUID *type, + UINTN *len, VOID *data ) { + struct efi_file *file = container_of ( this, struct efi_file, file ); + EFI_FILE_SYSTEM_INFO fsinfo; + struct image *image; + + /* Determine information to return */ + if ( memcmp ( type, &efi_file_info_id, sizeof ( *type ) ) == 0 ) { + + /* Get file information */ + DBGC ( file, "EFIFILE %s get file information\n", + efi_file_name ( file ) ); + return efi_file_info ( file->image, len, data ); + + } else if ( memcmp ( type, &efi_file_system_info_id, + sizeof ( *type ) ) == 0 ) { + + /* Get file system information */ + DBGC ( file, "EFIFILE %s get file system information\n", + efi_file_name ( file ) ); + memset ( &fsinfo, 0, sizeof ( fsinfo ) ); + fsinfo.ReadOnly = 1; + for_each_image ( image ) + fsinfo.VolumeSize += image->len; + return efi_file_varlen ( &fsinfo.Size, + SIZE_OF_EFI_FILE_SYSTEM_INFO, "iPXE", + len, data ); + } else { + + DBGC ( file, "EFIFILE %s cannot get information of type %s\n", + efi_file_name ( file ), efi_guid_ntoa ( type ) ); + return EFI_UNSUPPORTED; + } +} + +/** + * Set file information + * + * @v this EFI file + * @v type Type of information + * @v len Buffer size + * @v data Buffer + * @ret efirc EFI status code + */ +static EFI_STATUS EFIAPI +efi_file_set_info ( EFI_FILE_PROTOCOL *this, EFI_GUID *type, + UINTN len __unused, VOID *data __unused ) { + struct efi_file *file = container_of ( this, struct efi_file, file ); + + DBGC ( file, "EFIFILE %s cannot set information of type %s\n", + efi_file_name ( file ), efi_guid_ntoa ( type ) ); + return EFI_WRITE_PROTECTED; +} + +/** + * Flush file modified data + * + * @v this EFI file + * @v type Type of information + * @v len Buffer size + * @v data Buffer + * @ret efirc EFI status code + */ +static EFI_STATUS EFIAPI efi_file_flush ( EFI_FILE_PROTOCOL *this ) { + struct efi_file *file = container_of ( this, struct efi_file, file ); + + DBGC ( file, "EFIFILE %s flushed\n", efi_file_name ( file ) ); + return 0; +} + +/** Root directory */ +static struct efi_file efi_file_root = { + .file = { + .Revision = EFI_FILE_PROTOCOL_REVISION, + .Open = efi_file_open, + .Close = efi_file_close, + .Delete = efi_file_delete, + .Read = efi_file_read, + .Write = efi_file_write, + .GetPosition = efi_file_get_position, + .SetPosition = efi_file_set_position, + .GetInfo = efi_file_get_info, + .SetInfo = efi_file_set_info, + .Flush = efi_file_flush, + }, + .image = NULL, +}; + +/** + * Open root directory + * + * @v filesystem EFI simple file system + * @ret file EFI file handle + * @ret efirc EFI status code + */ +static EFI_STATUS EFIAPI +efi_file_open_volume ( EFI_SIMPLE_FILE_SYSTEM_PROTOCOL *filesystem __unused, + EFI_FILE_PROTOCOL **file ) { + + DBGC ( &efi_file_root, "EFIFILE open volume\n" ); + *file = &efi_file_root.file; + return 0; +} + +/** EFI simple file system protocol */ +static EFI_SIMPLE_FILE_SYSTEM_PROTOCOL efi_simple_file_system_protocol = { + .Revision = EFI_SIMPLE_FILE_SYSTEM_PROTOCOL_REVISION, + .OpenVolume = efi_file_open_volume, +}; + +/** Dummy block I/O reset */ +static EFI_STATUS EFIAPI +efi_block_io_reset ( EFI_BLOCK_IO_PROTOCOL *this __unused, BOOLEAN extended ) { + + DBGC ( &efi_file_root, "EFIFILE block %sreset\n", + ( extended ? "extended " : "" ) ); + return 0; +} + +/** Dummy block I/O read */ +static EFI_STATUS EFIAPI +efi_block_io_read_blocks ( EFI_BLOCK_IO_PROTOCOL *this __unused, UINT32 MediaId, + EFI_LBA lba, UINTN len, VOID *data ) { + + DBGC ( &efi_file_root, "EFIFILE block read ID %#08x LBA %#08llx -> " + "%p+%zx\n", MediaId, ( ( unsigned long long ) lba ), + data, ( ( size_t ) len ) ); + return EFI_NO_MEDIA; +} + +/** Dummy block I/O write */ +static EFI_STATUS EFIAPI +efi_block_io_write_blocks ( EFI_BLOCK_IO_PROTOCOL *this __unused, + UINT32 MediaId, EFI_LBA lba, UINTN len, + VOID *data ) { + + DBGC ( &efi_file_root, "EFIFILE block write ID %#08x LBA %#08llx <- " + "%p+%zx\n", MediaId, ( ( unsigned long long ) lba ), + data, ( ( size_t ) len ) ); + return EFI_NO_MEDIA; +} + +/** Dummy block I/O flush */ +static EFI_STATUS EFIAPI +efi_block_io_flush_blocks ( EFI_BLOCK_IO_PROTOCOL *this __unused ) { + + DBGC ( &efi_file_root, "EFIFILE block flush\n" ); + return 0; +} + +/** Dummy block I/O media */ +static EFI_BLOCK_IO_MEDIA efi_block_io_media = { + .MediaId = EFI_MEDIA_ID_MAGIC, + .MediaPresent = TRUE, + .ReadOnly = TRUE, + .BlockSize = 1, +}; + +/** Dummy EFI block I/O protocol */ +static EFI_BLOCK_IO_PROTOCOL efi_block_io_protocol = { + .Revision = EFI_BLOCK_IO_PROTOCOL_REVISION, + .Media = &efi_block_io_media, + .Reset = efi_block_io_reset, + .ReadBlocks = efi_block_io_read_blocks, + .WriteBlocks = efi_block_io_write_blocks, + .FlushBlocks = efi_block_io_flush_blocks, +}; + +/** Dummy disk I/O read */ +static EFI_STATUS EFIAPI +efi_disk_io_read_disk ( EFI_DISK_IO_PROTOCOL *this __unused, UINT32 MediaId, + UINT64 offset, UINTN len, VOID *data ) { + + DBGC ( &efi_file_root, "EFIFILE disk read ID %#08x offset %#08llx -> " + "%p+%zx\n", MediaId, ( ( unsigned long long ) offset ), + data, ( ( size_t ) len ) ); + return EFI_NO_MEDIA; +} + +/** Dummy disk I/O write */ +static EFI_STATUS EFIAPI +efi_disk_io_write_disk ( EFI_DISK_IO_PROTOCOL *this __unused, UINT32 MediaId, + UINT64 offset, UINTN len, VOID *data ) { + + DBGC ( &efi_file_root, "EFIFILE disk write ID %#08x offset %#08llx <- " + "%p+%zx\n", MediaId, ( ( unsigned long long ) offset ), + data, ( ( size_t ) len ) ); + return EFI_NO_MEDIA; +} + +/** Dummy EFI disk I/O protocol */ +static EFI_DISK_IO_PROTOCOL efi_disk_io_protocol = { + .Revision = EFI_DISK_IO_PROTOCOL_REVISION, + .ReadDisk = efi_disk_io_read_disk, + .WriteDisk = efi_disk_io_write_disk, +}; + +/** + * Install EFI simple file system protocol + * + * @v handle EFI handle + * @ret rc Return status code + */ +int efi_file_install ( EFI_HANDLE handle ) { + EFI_BOOT_SERVICES *bs = efi_systab->BootServices; + union { + EFI_DISK_IO_PROTOCOL *diskio; + void *interface; + } diskio; + EFI_STATUS efirc; + int rc; + + /* Install the simple file system protocol, block I/O + * protocol, and disk I/O protocol. We don't have a block + * device, but large parts of the EDK2 codebase make the + * assumption that file systems are normally attached to block + * devices, and so we create a dummy block device on the same + * handle just to keep things looking normal. + */ + if ( ( efirc = bs->InstallMultipleProtocolInterfaces ( + &handle, + &efi_block_io_protocol_guid, + &efi_block_io_protocol, + &efi_disk_io_protocol_guid, + &efi_disk_io_protocol, + &efi_simple_file_system_protocol_guid, + &efi_simple_file_system_protocol, NULL ) ) != 0 ) { + rc = -EEFI ( efirc ); + DBGC ( handle, "Could not install simple file system " + "protocols: %s\n", strerror ( rc ) ); + goto err_install; + } + + /* The FAT filesystem driver has a bug: if a block device + * contains no FAT filesystem but does have an + * EFI_SIMPLE_FILE_SYSTEM_PROTOCOL instance, the FAT driver + * will assume that it must have previously installed the + * EFI_SIMPLE_FILE_SYSTEM_PROTOCOL. This causes the FAT + * driver to claim control of our device, and to refuse to + * stop driving it, which prevents us from later uninstalling + * correctly. + * + * Work around this bug by opening the disk I/O protocol + * ourselves, thereby preventing the FAT driver from opening + * it. + * + * Note that the alternative approach of opening the block I/O + * protocol (and thereby in theory preventing DiskIo from + * attaching to the block I/O protocol) causes an endless loop + * of calls to our DRIVER_STOP method when starting the EFI + * shell. I have no idea why this is. + */ + if ( ( efirc = bs->OpenProtocol ( handle, &efi_disk_io_protocol_guid, + &diskio.interface, efi_image_handle, + handle, + EFI_OPEN_PROTOCOL_BY_DRIVER ) ) != 0){ + rc = -EEFI ( efirc ); + DBGC ( handle, "Could not open disk I/O protocol: %s\n", + strerror ( rc ) ); + DBGC_EFI_OPENERS ( handle, handle, &efi_disk_io_protocol_guid ); + goto err_open; + } + assert ( diskio.diskio == &efi_disk_io_protocol ); + + return 0; + + bs->CloseProtocol ( handle, &efi_disk_io_protocol_guid, + efi_image_handle, handle ); + err_open: + bs->UninstallMultipleProtocolInterfaces ( + handle, + &efi_simple_file_system_protocol_guid, + &efi_simple_file_system_protocol, + &efi_disk_io_protocol_guid, + &efi_disk_io_protocol, + &efi_block_io_protocol_guid, + &efi_block_io_protocol, NULL ); + err_install: + return rc; +} + +/** + * Uninstall EFI simple file system protocol + * + * @v handle EFI handle + */ +void efi_file_uninstall ( EFI_HANDLE handle ) { + EFI_BOOT_SERVICES *bs = efi_systab->BootServices; + EFI_STATUS efirc; + int rc; + + /* Close our own disk I/O protocol */ + bs->CloseProtocol ( handle, &efi_disk_io_protocol_guid, + efi_image_handle, handle ); + + /* We must install the file system protocol first, since + * otherwise the EDK2 code will attempt to helpfully uninstall + * it when the block I/O protocol is uninstalled, leading to a + * system lock-up. + */ + if ( ( efirc = bs->UninstallMultipleProtocolInterfaces ( + handle, + &efi_simple_file_system_protocol_guid, + &efi_simple_file_system_protocol, + &efi_disk_io_protocol_guid, + &efi_disk_io_protocol, + &efi_block_io_protocol_guid, + &efi_block_io_protocol, NULL ) ) != 0 ) { + rc = -EEFI ( efirc ); + DBGC ( handle, "Could not uninstall simple file system " + "protocols: %s\n", strerror ( rc ) ); + /* Oh dear */ + } +}