diff options
-rw-r--r-- | src/interface/efi/efi_file.c | 294 |
1 files changed, 172 insertions, 122 deletions
diff --git a/src/interface/efi/efi_file.c b/src/interface/efi/efi_file.c index a0bba1606..b232591dc 100644 --- a/src/interface/efi/efi_file.c +++ b/src/interface/efi/efi_file.c @@ -93,8 +93,18 @@ struct efi_file { 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 efi_file_initrd; +static struct efi_file_path efi_file_initrd; /** * Free EFI file @@ -358,8 +368,8 @@ efi_file_open ( EFI_FILE_PROTOCOL *this, EFI_FILE_PROTOCOL **new, } /* Allow magic initrd to be opened */ - if ( strcasecmp ( name, efi_file_initrd.name ) == 0 ) - return efi_file_open_fixed ( &efi_file_initrd, new ); + if ( strcasecmp ( name, efi_file_initrd.file.name ) == 0 ) + return efi_file_open_fixed ( &efi_file_initrd.file, new ); /* Identify image */ image = efi_file_find ( name ); @@ -701,10 +711,10 @@ static EFI_STATUS EFIAPI efi_file_flush ( EFI_FILE_PROTOCOL *this ) { * @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 ) { +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; @@ -752,30 +762,6 @@ static struct efi_file efi_file_root = { .name = "", }; -/** Magic initrd file */ -static struct efi_file efi_file_initrd = { - .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, -}; - /** Linux initrd fixed device path */ static struct { VENDOR_DEVICE_PATH vendor; @@ -796,6 +782,33 @@ static struct { }, }; +/** 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 * @@ -905,107 +918,148 @@ static EFI_DISK_IO_PROTOCOL efi_disk_io_protocol = { }; /** - * (Re)install fixed device path file + * Claim use of fixed device path * - * @v path Device path - * @v load Load file protocol, or NULL to uninstall protocol + * @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. - * - * The design choice in Linux of using a single fixed device path - * makes this 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. Subsequent bootloaders - * must locate the existing handle and replace the load file protocol - * instance with their own. + * 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_install ( EFI_DEVICE_PATH_PROTOCOL *path, - EFI_LOAD_FILE2_PROTOCOL *load ) { +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 *path_copy; VOID *old; - size_t path_len; EFI_STATUS efirc; int rc; - /* Locate or install the handle with this device path */ - end = path; - if ( ( ( efirc = bs->LocateDevicePath ( &efi_device_path_protocol_guid, - &end, &handle ) ) == 0 ) && - ( end->Type == END_DEVICE_PATH_TYPE ) ) { - - /* Exact match: reuse (or uninstall from) this handle */ - if ( load ) { - DBGC ( path, "EFIFILE %s reusing existing handle\n", - efi_devpath_text ( path ) ); - } + /* Sanity check */ + assert ( file->handle == NULL ); - } else { + /* 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; + } - /* Allocate a permanent copy of the device path, since - * this handle will survive after this binary is - * unloaded. - */ - path_len = ( efi_path_len ( path ) + sizeof ( *end ) ); - if ( ( efirc = bs->AllocatePool ( EfiBootServicesData, path_len, - &path_copy ) ) != 0 ) { - rc = -EEFI ( efirc ); - DBGC ( path, "EFIFILE %s could not allocate device path: " - "%s\n", efi_devpath_text ( path ), strerror ( rc ) ); - return rc; - } - memcpy ( path_copy, path, path_len ); + /* 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; + } - /* Create a new handle with this device path */ - handle = NULL; - if ( ( efirc = bs->InstallMultipleProtocolInterfaces ( - &handle, - &efi_device_path_protocol_guid, path_copy, + /* 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 ( path, "EFIFILE %s could not create handle: %s\n", - efi_devpath_text ( path ), strerror ( rc ) ); - return rc; - } + 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; } - /* Uninstall existing load file protocol instance, if any */ - if ( ( ( efirc = bs->HandleProtocol ( handle, &efi_load_file2_protocol_guid, - &old ) ) == 0 ) && - ( ( efirc = bs->UninstallMultipleProtocolInterfaces ( - handle, - &efi_load_file2_protocol_guid, old, - NULL ) ) != 0 ) ) { + 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 ( path, "EFIFILE %s could not uninstall %s: %s\n", - efi_devpath_text ( path ), - efi_guid_ntoa ( &efi_load_file2_protocol_guid ), - strerror ( rc ) ); + DBGC ( file, "EFIFILE %s could not install %s: %s\n", + efi_file_name ( &file->file ), + efi_devpath_text ( file->path ), strerror ( rc ) ); return rc; } - /* Install new load file protocol instance, if applicable */ - if ( ( load != NULL ) && - ( ( efirc = bs->InstallMultipleProtocolInterfaces ( - &handle, - &efi_load_file2_protocol_guid, load, - NULL ) ) != 0 ) ) { + 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 ( path, "EFIFILE %s could not install %s: %s\n", - efi_devpath_text ( path ), + 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 ) ); - return rc; + /* Continue uninstalling */ } - return 0; + /* Mark handle as uninstalled */ + file->handle = NULL; } /** @@ -1016,7 +1070,6 @@ static int efi_file_path_install ( EFI_DEVICE_PATH_PROTOCOL *path, */ int efi_file_install ( EFI_HANDLE handle ) { EFI_BOOT_SERVICES *bs = efi_systab->BootServices; - EFI_LOAD_FILE2_PROTOCOL *load; union { EFI_DISK_IO_PROTOCOL *diskio; void *interface; @@ -1079,24 +1132,21 @@ int efi_file_install ( EFI_HANDLE handle ) { } assert ( diskio.diskio == &efi_disk_io_protocol ); - /* Install Linux initrd fixed device path file - * - * Install the device path handle unconditionally, since we - * are definitively the bootloader providing the initrd, if - * any, to the booted image. Install the load file protocol - * instance only if the initrd is non-empty, since Linux does - * not gracefully handle a zero-length initrd. - */ - load = ( have_images() ? &efi_file_initrd.load : NULL ); - if ( ( rc = efi_file_path_install ( &efi_file_initrd_path.vendor.Header, - load ) ) != 0 ) { - goto err_initrd; + /* 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 */ + if ( have_images() && + ( ( rc = efi_file_path_install ( &efi_file_initrd ) ) != 0 ) ) { + goto err_initrd_install; } return 0; - efi_file_path_install ( &efi_file_initrd_path.vendor.Header, NULL ); - err_initrd: + 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: @@ -1123,7 +1173,7 @@ void efi_file_uninstall ( EFI_HANDLE handle ) { int rc; /* Uninstall Linux initrd fixed device path file */ - efi_file_path_install ( &efi_file_initrd_path.vendor.Header, NULL ); + efi_file_path_uninstall ( &efi_file_initrd ); /* Close our own disk I/O protocol */ bs->CloseProtocol ( handle, &efi_disk_io_protocol_guid, |