/* * 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. * * 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 file protocols * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /** EFI media ID */ #define EFI_MEDIA_ID_MAGIC 0x69505845 /** Linux initrd fixed device path vendor GUID */ #define LINUX_INITRD_VENDOR_GUID \ { 0x5568e427, 0x68fc, 0x4f3d, \ { 0xac, 0x74, 0xca, 0x55, 0x52, 0x31, 0xcc, 0x68 } } /** An EFI virtual file reader */ struct efi_file_reader { /** EFI file */ struct efi_file *file; /** Position within virtual file */ size_t pos; /** Output data buffer */ void *data; /** Length of output data buffer */ size_t len; }; /** An EFI file */ struct efi_file { /** Reference count */ struct refcnt refcnt; /** EFI file protocol */ EFI_FILE_PROTOCOL file; /** EFI load file protocol */ EFI_LOAD_FILE2_PROTOCOL load; /** Image (if any) */ struct image *image; /** Filename */ const char *name; /** Current file position */ size_t pos; /** * Read from file * * @v reader File reader * @ret len Length read */ size_t ( * read ) ( struct efi_file_reader *reader ); }; /** An EFI fixed device path file */ struct efi_file_path { /** EFI file */ struct efi_file file; /** Device path */ EFI_DEVICE_PATH_PROTOCOL *path; /** EFI handle */ EFI_HANDLE handle; }; static struct efi_file efi_file_root; static struct efi_file_path efi_file_initrd; /** * Free EFI file * * @v refcnt Reference count */ static void efi_file_free ( struct refcnt *refcnt ) { struct efi_file *file = container_of ( refcnt, struct efi_file, refcnt ); image_put ( file->image ); free ( file ); } /** * 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 == &efi_file_root ? "" : file->name ); } /** * Find EFI file image * * @v name Filename * @ret image Image, or NULL */ static struct image * efi_file_find ( const char *name ) { struct image *image; /* Find image */ for_each_image ( image ) { if ( strcasecmp ( image->name, name ) == 0 ) return image; } return NULL; } /** * Get length of EFI file * * @v file EFI file * @ret len Length of file */ static size_t efi_file_len ( struct efi_file *file ) { struct efi_file_reader reader; /* If this is the root directory, then treat as length zero */ if ( ! file->read ) return 0; /* Initialise reader */ reader.file = file; reader.pos = 0; reader.data = NULL; reader.len = 0; /* Perform dummy read to determine file length */ file->read ( &reader ); return reader.pos; } /** * Read chunk of EFI file * * @v reader EFI file reader * @v data Input data, or UNULL to zero-fill * @v len Length of input data * @ret len Length of output data */ static size_t efi_file_read_chunk ( struct efi_file_reader *reader, userptr_t data, size_t len ) { struct efi_file *file = reader->file; size_t offset; /* Calculate offset into input data */ offset = ( file->pos - reader->pos ); /* Consume input data range */ reader->pos += len; /* Calculate output length */ if ( offset < len ) { len -= offset; } else { len = 0; } if ( len > reader->len ) len = reader->len; /* Copy or zero output data */ if ( data ) { copy_from_user ( reader->data, data, offset, len ); } else { memset ( reader->data, 0, len ); } /* Consume output buffer */ file->pos += len; reader->data += len; reader->len -= len; return len; } /** * Read from image-backed file * * @v reader EFI file reader * @ret len Length read */ static size_t efi_file_read_image ( struct efi_file_reader *reader ) { struct efi_file *file = reader->file; struct image *image = file->image; /* Read from file */ return efi_file_read_chunk ( reader, image->data, image->len ); } /** * Read from magic initrd file * * @v reader EFI file reader * @ret len Length read */ static size_t efi_file_read_initrd ( struct efi_file_reader *reader ) { struct efi_file *file = reader->file; struct cpio_header cpio; struct image *image; const char *name; size_t pad_len; size_t cpio_len; size_t name_len; size_t len; /* Read from file */ len = 0; for_each_image ( image ) { /* Skip hidden images */ if ( image->flags & IMAGE_HIDDEN ) continue; /* Pad to alignment boundary */ pad_len = ( ( -reader->pos ) & ( INITRD_ALIGN - 1 ) ); if ( pad_len ) { DBGC ( file, "EFIFILE %s [%#08zx,%#08zx) pad\n", efi_file_name ( file ), reader->pos, ( reader->pos + pad_len ) ); } len += efi_file_read_chunk ( reader, UNULL, pad_len ); /* Read CPIO header, if applicable */ cpio_len = cpio_header ( image, &cpio ); if ( cpio_len ) { name = cpio_name ( image ); name_len = cpio_name_len ( image ); pad_len = ( cpio_len - sizeof ( cpio ) - name_len ); DBGC ( file, "EFIFILE %s [%#08zx,%#08zx) %s header\n", efi_file_name ( file ), reader->pos, ( reader->pos + cpio_len ), image->name ); len += efi_file_read_chunk ( reader, virt_to_user ( &cpio ), sizeof ( cpio ) ); len += efi_file_read_chunk ( reader, virt_to_user ( name ), name_len ); len += efi_file_read_chunk ( reader, UNULL, pad_len ); } /* Read file data */ DBGC ( file, "EFIFILE %s [%#08zx,%#08zx) %s\n", efi_file_name ( file ), reader->pos, ( reader->pos + image->len ), image->name ); len += efi_file_read_chunk ( reader, image->data, image->len ); } return len; } /** * Open fixed file * * @v file EFI file * @v wname Filename * @v new New EFI file * @ret efirc EFI status code */ static EFI_STATUS efi_file_open_fixed ( struct efi_file *file, const wchar_t *wname, EFI_FILE_PROTOCOL **new ) { /* Increment reference count */ ref_get ( &file->refcnt ); /* Return opened file */ *new = &file->file; DBGC ( file, "EFIFILE %s opened via %ls\n", efi_file_name ( file ), wname ); return 0; } /** * Associate file with image * * @v file EFI file * @v image Image */ static void efi_file_image ( struct efi_file *file, struct image *image ) { file->image = image; file->name = image->name; file->read = efi_file_read_image; } /** * Open image-backed file * * @v image Image * @v wname Filename * @v new New EFI file * @ret efirc EFI status code */ static EFI_STATUS efi_file_open_image ( struct image *image, const wchar_t *wname, EFI_FILE_PROTOCOL **new ) { struct efi_file *file; /* Allocate and initialise file */ file = zalloc ( sizeof ( *file ) ); if ( ! file ) return EFI_OUT_OF_RESOURCES; ref_init ( &file->refcnt, efi_file_free ); memcpy ( &file->file, &efi_file_root.file, sizeof ( file->file ) ); memcpy ( &file->load, &efi_file_root.load, sizeof ( file->load ) ); efi_file_image ( file, image_get ( image ) ); /* Return opened file */ *new = &file->file; DBGC ( file, "EFIFILE %s opened via %ls\n", efi_file_name ( file ), wname ); return 0; } /** * 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, UINT64 attributes __unused ) { struct efi_file *file = container_of ( this, struct efi_file, file ); char buf[ wcslen ( wname ) + 1 /* NUL */ ]; struct image *image; char *name; char *sep; /* Convert name to ASCII */ snprintf ( buf, sizeof ( buf ), "%ls", wname ); name = buf; /* Initial '\' indicates opening from the root directory */ while ( *name == '\\' ) { file = &efi_file_root; name++; } /* Allow root directory itself to be opened */ if ( ( name[0] == '\0' ) || ( name[0] == '.' ) ) return efi_file_open_fixed ( &efi_file_root, wname, new ); /* Fail unless opening from the root */ if ( file != &efi_file_root ) { DBGC ( file, "EFIFILE %s is not a directory\n", efi_file_name ( file ) ); 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", name, mode ); return EFI_WRITE_PROTECTED; } /* Allow registered images to be opened */ if ( ( image = efi_file_find ( name ) ) != NULL ) return efi_file_open_image ( image, wname, new ); /* Allow magic initrd to be opened */ if ( strcasecmp ( name, efi_file_initrd.file.name ) == 0 ) { return efi_file_open_fixed ( &efi_file_initrd.file, wname, new ); } /* Allow currently selected image to be opened as "grub*.efi", * to work around buggy versions of the UEFI shim. */ if ( ( strncasecmp ( name, "grub", 4 ) == 0 ) && ( ( sep = strrchr ( name, '.' ) ) != NULL ) && ( strcasecmp ( sep, ".efi" ) == 0 ) && ( ( image = find_image_tag ( &selected_image ) ) != NULL ) ) { return efi_file_open_image ( image, wname, new ); } DBGC ( file, "EFIFILE %ls does not exist\n", wname ); return EFI_NOT_FOUND; } /** * 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 ); /* Close file */ DBGC ( file, "EFIFILE %s closed\n", efi_file_name ( file ) ); ref_put ( &file->refcnt ); 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 file EFI file * @v len Length of data buffer * @v data Data buffer * @ret efirc EFI status code */ static EFI_STATUS efi_file_info ( struct efi_file *file, UINTN *len, VOID *data ) { EFI_FILE_INFO info; size_t file_len; /* Get file length */ file_len = efi_file_len ( file ); /* Populate file information */ memset ( &info, 0, sizeof ( info ) ); info.FileSize = file_len; info.PhysicalSize = file_len; info.Attribute = EFI_FILE_READ_ONLY; if ( file == &efi_file_root ) info.Attribute |= EFI_FILE_DIRECTORY; return efi_file_varlen ( &info.Size, SIZE_OF_EFI_FILE_INFO, file->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 efi_file entry; struct image *image; unsigned int index; /* Construct directory entries for image-backed files */ index = file->pos; for_each_image ( image ) { /* Skip hidden images */ if ( image->flags & IMAGE_HIDDEN ) continue; /* Skip preceding images */ if ( index-- ) continue; /* Construct directory entry */ efi_file_image ( &entry, image ); efirc = efi_file_info ( &entry, 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 ); struct efi_file_reader reader; size_t pos = file->pos; /* If this is the root directory, then construct a directory entry */ if ( ! file->read ) return efi_file_read_dir ( file, len, data ); /* Initialise reader */ reader.file = file; reader.pos = 0; reader.data = data; reader.len = *len; /* Read from the file */ DBGC ( file, "EFIFILE %s read [%#08zx,%#08zx)\n", efi_file_name ( file ), pos, ( ( size_t ) ( pos + *len ) ) ); *len = file->read ( &reader ); assert ( ( pos + *len ) == file->pos ); 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 ); size_t len; /* Get file length */ len = efi_file_len ( file ); /* Check for the magic end-of-file value */ if ( position == 0xffffffffffffffffULL ) position = len; /* Fail if we attempt to seek past the end of the file (since * we do not support writes). */ if ( position > len ) { DBGC ( file, "EFIFILE %s cannot seek to %#08llx of %#08zx\n", efi_file_name ( file ), position, 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, 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; } /** * Load file * * @v this EFI file loader * @v path File path * @v boot Boot policy * @v len Buffer size * @v data Buffer, or NULL * @ret efirc EFI status code */ static EFI_STATUS EFIAPI efi_file_load ( EFI_LOAD_FILE2_PROTOCOL *this, EFI_DEVICE_PATH_PROTOCOL *path __unused, BOOLEAN boot __unused, UINTN *len, VOID *data ) { struct efi_file *file = container_of ( this, struct efi_file, load ); size_t max_len; size_t file_len; EFI_STATUS efirc; /* Calculate maximum length */ max_len = ( data ? *len : 0 ); DBGC ( file, "EFIFILE %s load at %p+%#zx\n", efi_file_name ( file ), data, max_len ); /* Check buffer size */ file_len = efi_file_len ( file ); if ( file_len > max_len ) { *len = file_len; return EFI_BUFFER_TOO_SMALL; } /* Read from file */ if ( ( efirc = efi_file_read ( &file->file, len, data ) ) != 0 ) return efirc; return 0; } /** Root directory */ static struct efi_file efi_file_root = { .refcnt = REF_INIT ( ref_no_free ), .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, }, .load = { .LoadFile = efi_file_load, }, .image = NULL, .name = "", }; /** Linux initrd fixed device path */ static struct { VENDOR_DEVICE_PATH vendor; EFI_DEVICE_PATH_PROTOCOL end; } __attribute__ (( packed )) efi_file_initrd_path = { .vendor = { .Header = { .Type = MEDIA_DEVICE_PATH, .SubType = MEDIA_VENDOR_DP, .Length[0] = sizeof ( efi_file_initrd_path.vendor ), }, .Guid = LINUX_INITRD_VENDOR_GUID, }, .end = { .Type = END_DEVICE_PATH_TYPE, .SubType = END_ENTIRE_DEVICE_PATH_SUBTYPE, .Length[0] = sizeof ( efi_file_initrd_path.end ), }, }; /** Magic initrd file */ static struct efi_file_path efi_file_initrd = { .file = { .refcnt = REF_INIT ( ref_no_free ), .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, }, .load = { .LoadFile = efi_file_load, }, .image = NULL, .name = "initrd.magic", .read = efi_file_read_initrd, }, .path = &efi_file_initrd_path.vendor.Header, }; /** * 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" ); return efi_file_open_fixed ( &efi_file_root, L"", file ); } /** 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, }; /** * Claim use of fixed device path * * @v file Fixed device path file * @ret rc Return status code * * The design choice in Linux of using a single fixed device path is * unfortunately messy to support, since device paths must be unique * within a system. When multiple bootloaders are used (e.g. GRUB * loading iPXE loading Linux) then only one bootloader can ever * install the device path onto a handle. Bootloaders must therefore * be prepared to locate an existing handle and uninstall its device * path protocol instance before installing a new handle with the * required device path. */ static int efi_file_path_claim ( struct efi_file_path *file ) { EFI_BOOT_SERVICES *bs = efi_systab->BootServices; EFI_DEVICE_PATH_PROTOCOL *end; EFI_HANDLE handle; VOID *old; EFI_STATUS efirc; int rc; /* Sanity check */ assert ( file->handle == NULL ); /* Locate handle with this device path, if any */ end = file->path; if ( ( ( efirc = bs->LocateDevicePath ( &efi_device_path_protocol_guid, &end, &handle ) ) != 0 ) || ( end->Type != END_DEVICE_PATH_TYPE ) ) { return 0; } /* Locate device path protocol on this handle */ if ( ( ( efirc = bs->HandleProtocol ( handle, &efi_device_path_protocol_guid, &old ) ) != 0 ) ) { rc = -EEFI ( efirc ); DBGC ( file, "EFIFILE %s could not locate %s: %s\n", efi_file_name ( &file->file ), efi_devpath_text ( file->path ), strerror ( rc ) ); return rc; } /* Uninstall device path protocol, leaving other protocols untouched */ if ( ( efirc = bs->UninstallMultipleProtocolInterfaces ( handle, &efi_device_path_protocol_guid, old, NULL ) ) != 0 ) { rc = -EEFI ( efirc ); DBGC ( file, "EFIFILE %s could not claim %s: %s\n", efi_file_name ( &file->file ), efi_devpath_text ( file->path ), strerror ( rc ) ); return rc; } DBGC ( file, "EFIFILE %s claimed %s", efi_file_name ( &file->file ), efi_devpath_text ( file->path ) ); DBGC ( file, " from %s\n", efi_handle_name ( handle ) ); return 0; } /** * Install fixed device path file * * @v file Fixed device path file * @ret rc Return status code * * Linux 5.7 added the ability to autodetect an initrd by searching * for a handle via a fixed vendor-specific "Linux initrd device path" * and then locating and using the EFI_LOAD_FILE2_PROTOCOL instance on * that handle. */ static int efi_file_path_install ( struct efi_file_path *file ) { EFI_BOOT_SERVICES *bs = efi_systab->BootServices; EFI_STATUS efirc; int rc; /* Sanity check */ assert ( file->handle == NULL ); /* Create a new handle with this device path */ if ( ( efirc = bs->InstallMultipleProtocolInterfaces ( &file->handle, &efi_device_path_protocol_guid, file->path, &efi_load_file2_protocol_guid, &file->file.load, NULL ) ) != 0 ) { rc = -EEFI ( efirc ); DBGC ( file, "EFIFILE %s could not install %s: %s\n", efi_file_name ( &file->file ), efi_devpath_text ( file->path ), strerror ( rc ) ); return rc; } DBGC ( file, "EFIFILE %s installed as %s\n", efi_file_name ( &file->file ), efi_devpath_text ( file->path ) ); return 0; } /** * Uninstall fixed device path file * * @v file Fixed device path file * @ret rc Return status code */ static void efi_file_path_uninstall ( struct efi_file_path *file ) { EFI_BOOT_SERVICES *bs = efi_systab->BootServices; EFI_STATUS efirc; int rc; /* Do nothing if file is already uninstalled */ if ( ! file->handle ) return; /* Uninstall protocols. Do this via two separate calls, in * case another executable has already uninstalled the device * path protocol from our handle. */ if ( ( efirc = bs->UninstallMultipleProtocolInterfaces ( file->handle, &efi_device_path_protocol_guid, file->path, NULL ) ) != 0 ) { rc = -EEFI ( efirc ); DBGC ( file, "EFIFILE %s could not uninstall %s: %s\n", efi_file_name ( &file->file ), efi_devpath_text ( file->path ), strerror ( rc ) ); /* Continue uninstalling */ } if ( ( efirc = bs->UninstallMultipleProtocolInterfaces ( file->handle, &efi_load_file2_protocol_guid, &file->file.load, NULL ) ) != 0 ) { rc = -EEFI ( efirc ); DBGC ( file, "EFIFILE %s could not uninstall %s: %s\n", efi_file_name ( &file->file ), efi_guid_ntoa ( &efi_load_file2_protocol_guid ), strerror ( rc ) ); /* Continue uninstalling */ } /* Mark handle as uninstalled */ file->handle = NULL; } /** * 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; struct image *image; EFI_STATUS efirc; int rc; /* Reset root directory state */ efi_file_root.pos = 0; /* 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 ); /* Claim Linux initrd fixed device path */ if ( ( rc = efi_file_path_claim ( &efi_file_initrd ) ) != 0 ) goto err_initrd_claim; /* Install Linux initrd fixed device path file if non-empty */ for_each_image ( image ) { if ( image->flags & IMAGE_HIDDEN ) continue; if ( ( rc = efi_file_path_install ( &efi_file_initrd ) ) != 0 ) goto err_initrd_install; break; } return 0; efi_file_path_uninstall ( &efi_file_initrd ); err_initrd_install: err_initrd_claim: 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; /* Uninstall Linux initrd fixed device path file */ efi_file_path_uninstall ( &efi_file_initrd ); /* 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 */ } }