diff options
author | AKASHI Takahiro <takahiro.akashi@linaro.org> | 2020-11-30 18:12:11 +0900 |
---|---|---|
committer | Heinrich Schuchardt <xypron.glpk@gmx.de> | 2020-12-03 21:22:50 +0100 |
commit | 8d99026f06978ddf2ed72ccaed6cd3ad0887e4e5 (patch) | |
tree | fb4b261aab678d2a83380a674b40cee1fe21da9d /lib/efi_loader/efi_capsule.c | |
parent | ed9349c697711bd2d1b69b0877bc3d96fc664a37 (diff) |
efi_loader: capsule: support firmware update
A capsule tagged with the guid, EFI_FIRMWARE_MANAGEMENT_CAPSULE_ID_GUID,
is handled as a firmware update object.
What efi_update_capsule() basically does is to load any firmware management
protocol (or fmp) drivers contained in a capsule, find out an appropriate
fmp driver and then invoke its set_image() interface against each binary
in a capsule.
In this commit, however, loading drivers is not supported.
The result of applying a capsule is set to be stored in "CapsuleXXXX"
variable, but its implementation is deferred to a fmp driver.
Signed-off-by: AKASHI Takahiro <takahiro.akashi@linaro.org>
Diffstat (limited to 'lib/efi_loader/efi_capsule.c')
-rw-r--r-- | lib/efi_loader/efi_capsule.c | 252 |
1 files changed, 240 insertions, 12 deletions
diff --git a/lib/efi_loader/efi_capsule.c b/lib/efi_loader/efi_capsule.c index b3c7d1b735..3e7ad470d4 100644 --- a/lib/efi_loader/efi_capsule.c +++ b/lib/efi_loader/efi_capsule.c @@ -15,6 +15,10 @@ #include <sort.h> const efi_guid_t efi_guid_capsule_report = EFI_CAPSULE_REPORT_GUID; +static const efi_guid_t efi_guid_firmware_management_capsule_id = + EFI_FIRMWARE_MANAGEMENT_CAPSULE_ID_GUID; +const efi_guid_t efi_guid_firmware_management_protocol = + EFI_FIRMWARE_MANAGEMENT_PROTOCOL_GUID; #ifdef CONFIG_EFI_CAPSULE_ON_DISK /* for file system access */ @@ -85,9 +89,214 @@ void set_capsule_result(int index, struct efi_capsule_header *capsule, EFI_VARIABLE_RUNTIME_ACCESS, sizeof(result), &result); if (ret) - printf("EFI: creating %ls failed\n", variable_name16); + log_err("EFI: creating %ls failed\n", variable_name16); } +#ifdef CONFIG_EFI_CAPSULE_FIRMWARE_MANAGEMENT +/** + * efi_fmp_find - search for Firmware Management Protocol drivers + * @image_type: Image type guid + * @instance: Instance number + * @handles: Handles of FMP drivers + * @no_handles: Number of handles + * + * Search for Firmware Management Protocol drivers, matching the image + * type, @image_type and the machine instance, @instance, from the list, + * @handles. + * + * Return: + * * Protocol instance - on success + * * NULL - on failure + */ +static struct efi_firmware_management_protocol * +efi_fmp_find(efi_guid_t *image_type, u64 instance, efi_handle_t *handles, + efi_uintn_t no_handles) +{ + efi_handle_t *handle; + struct efi_firmware_management_protocol *fmp; + struct efi_firmware_image_descriptor *image_info, *desc; + efi_uintn_t info_size, descriptor_size; + u32 descriptor_version; + u8 descriptor_count; + u32 package_version; + u16 *package_version_name; + bool found = false; + int i, j; + efi_status_t ret; + + for (i = 0, handle = handles; i < no_handles; i++, handle++) { + ret = EFI_CALL(efi_handle_protocol( + *handle, + &efi_guid_firmware_management_protocol, + (void **)&fmp)); + if (ret != EFI_SUCCESS) + continue; + + /* get device's image info */ + info_size = 0; + image_info = NULL; + descriptor_version = 0; + descriptor_count = 0; + descriptor_size = 0; + package_version = 0; + package_version_name = NULL; + ret = EFI_CALL(fmp->get_image_info(fmp, &info_size, + image_info, + &descriptor_version, + &descriptor_count, + &descriptor_size, + &package_version, + &package_version_name)); + if (ret != EFI_BUFFER_TOO_SMALL) + goto skip; + + image_info = malloc(info_size); + if (!image_info) + goto skip; + + ret = EFI_CALL(fmp->get_image_info(fmp, &info_size, + image_info, + &descriptor_version, + &descriptor_count, + &descriptor_size, + &package_version, + &package_version_name)); + if (ret != EFI_SUCCESS || + descriptor_version != EFI_FIRMWARE_IMAGE_DESCRIPTOR_VERSION) + goto skip; + + /* matching */ + for (j = 0, desc = image_info; j < descriptor_count; + j++, desc = (void *)desc + descriptor_size) { + log_debug("+++ desc[%d] index: %d, name: %ls\n", + j, desc->image_index, desc->image_id_name); + if (!guidcmp(&desc->image_type_id, image_type) && + (!instance || + !desc->hardware_instance || + desc->hardware_instance == instance)) + found = true; + } + +skip: + efi_free_pool(package_version_name); + free(image_info); + EFI_CALL(efi_close_protocol( + (efi_handle_t)fmp, + &efi_guid_firmware_management_protocol, + NULL, NULL)); + if (found) + return fmp; + } + + return NULL; +} + +/** + * efi_capsule_update_firmware - update firmware from capsule + * @capsule_data: Capsule + * + * Update firmware, using a capsule, @capsule_data. Loading any FMP + * drivers embedded in a capsule is not supported. + * + * Return: status code + */ +static efi_status_t efi_capsule_update_firmware( + struct efi_capsule_header *capsule_data) +{ + struct efi_firmware_management_capsule_header *capsule; + struct efi_firmware_management_capsule_image_header *image; + size_t capsule_size; + void *image_binary, *vendor_code; + efi_handle_t *handles; + efi_uintn_t no_handles; + int item; + struct efi_firmware_management_protocol *fmp; + u16 *abort_reason; + efi_status_t ret = EFI_SUCCESS; + + /* sanity check */ + if (capsule_data->header_size < sizeof(*capsule) || + capsule_data->header_size >= capsule_data->capsule_image_size) + return EFI_INVALID_PARAMETER; + + capsule = (void *)capsule_data + capsule_data->header_size; + capsule_size = capsule_data->capsule_image_size + - capsule_data->header_size; + + if (capsule->version != 0x00000001) + return EFI_UNSUPPORTED; + + handles = NULL; + ret = EFI_CALL(efi_locate_handle_buffer( + BY_PROTOCOL, + &efi_guid_firmware_management_protocol, + NULL, &no_handles, (efi_handle_t **)&handles)); + if (ret != EFI_SUCCESS) + return EFI_UNSUPPORTED; + + /* Payload */ + for (item = capsule->embedded_driver_count; + item < capsule->embedded_driver_count + + capsule->payload_item_count; item++) { + /* sanity check */ + if ((capsule->item_offset_list[item] + sizeof(*image) + >= capsule_size)) { + log_err("EFI: A capsule has not enough data\n"); + ret = EFI_INVALID_PARAMETER; + goto out; + } + + image = (void *)capsule + capsule->item_offset_list[item]; + + if (image->version != 0x00000003) { + ret = EFI_UNSUPPORTED; + goto out; + } + + /* find a device for update firmware */ + /* TODO: should we pass index as well, or nothing but type? */ + fmp = efi_fmp_find(&image->update_image_type_id, + image->update_hardware_instance, + handles, no_handles); + if (!fmp) { + log_err("EFI Capsule: driver not found for firmware type: %pUl, hardware instance: %lld\n", + &image->update_image_type_id, + image->update_hardware_instance); + ret = EFI_UNSUPPORTED; + goto out; + } + + /* do update */ + image_binary = (void *)image + sizeof(*image); + vendor_code = image_binary + image->update_image_size; + + abort_reason = NULL; + ret = EFI_CALL(fmp->set_image(fmp, image->update_image_index, + image_binary, + image->update_image_size, + vendor_code, NULL, + &abort_reason)); + if (ret != EFI_SUCCESS) { + log_err("EFI Capsule: firmware update failed: %ls\n", + abort_reason); + efi_free_pool(abort_reason); + goto out; + } + } + +out: + efi_free_pool(handles); + + return ret; +} +#else +static efi_status_t efi_capsule_update_firmware( + struct efi_capsule_header *capsule_data) +{ + return EFI_UNSUPPORTED; +} +#endif /* CONFIG_EFI_CAPSULE_FIRMWARE_MANAGEMENT */ + /** * efi_update_capsule() - process information from operating system * @capsule_header_array: Array of virtual address pointers @@ -118,9 +327,29 @@ efi_status_t EFIAPI efi_update_capsule( goto out; } - ret = EFI_UNSUPPORTED; + ret = EFI_SUCCESS; for (i = 0, capsule = *capsule_header_array; i < capsule_count; i++, capsule = *(++capsule_header_array)) { + /* sanity check */ + if (capsule->header_size < sizeof(*capsule) || + capsule->capsule_image_size < sizeof(*capsule)) { + log_err("EFI: A capsule has not enough data\n"); + continue; + } + + log_debug("Capsule[%d] (guid:%pUl)\n", + i, &capsule->capsule_guid); + if (!guidcmp(&capsule->capsule_guid, + &efi_guid_firmware_management_capsule_id)) { + ret = efi_capsule_update_firmware(capsule); + } else { + log_err("EFI: not support capsule type: %pUl\n", + &capsule->capsule_guid); + ret = EFI_UNSUPPORTED; + } + + if (ret != EFI_SUCCESS) + goto out; } out: return EFI_EXIT(ret); @@ -265,7 +494,7 @@ static efi_status_t find_boot_device(void) if (ret == EFI_SUCCESS || ret == EFI_BUFFER_TOO_SMALL) { /* BootNext does exist here */ if (ret == EFI_BUFFER_TOO_SMALL || size != sizeof(u16)) { - printf("BootNext must be 16-bit integer\n"); + log_err("BootNext must be 16-bit integer\n"); goto skip; } sprintf((char *)boot_var, "Boot%04X", bootnext); @@ -323,7 +552,7 @@ out: u16 *path_str; path_str = efi_dp_str(boot_dev); - EFI_PRINT("EFI Capsule: bootdev is %ls\n", path_str); + log_debug("EFI Capsule: bootdev is %ls\n", path_str); efi_free_pool(path_str); volume = efi_fs_from_path(boot_dev); @@ -363,7 +592,7 @@ static efi_status_t efi_capsule_scan_dir(u16 ***files, unsigned int *num) ret = find_boot_device(); if (ret == EFI_NOT_FOUND) { - EFI_PRINT("EFI Capsule: bootdev is not set\n"); + log_debug("EFI Capsule: bootdev is not set\n"); *num = 0; return EFI_SUCCESS; } else if (ret != EFI_SUCCESS) { @@ -619,20 +848,19 @@ efi_status_t efi_launch_capsules(void) /* Launch capsules */ for (i = 0, ++index; i < nfiles; i++, index++) { - EFI_PRINT("capsule from %ls ...\n", files[i]); + log_debug("capsule from %ls ...\n", files[i]); if (index > 0xffff) index = 0; ret = efi_capsule_read_file(files[i], &capsule); if (ret == EFI_SUCCESS) { ret = EFI_CALL(efi_update_capsule(&capsule, 1, 0)); if (ret != EFI_SUCCESS) - printf("EFI Capsule update failed at %ls\n", - files[i]); + log_err("EFI Capsule update failed at %ls\n", + files[i]); free(capsule); } else { - printf("EFI: reading capsule failed: %ls\n", - files[i]); + log_err("EFI: reading capsule failed: %ls\n", files[i]); } /* create CapsuleXXXX */ set_capsule_result(index, capsule, ret); @@ -640,8 +868,8 @@ efi_status_t efi_launch_capsules(void) /* delete a capsule either in case of success or failure */ ret = efi_capsule_delete_file(files[i]); if (ret != EFI_SUCCESS) - printf("EFI: deleting a capsule file failed: %ls\n", - files[i]); + log_err("EFI: deleting a capsule file failed: %ls\n", + files[i]); } efi_capsule_scan_done(); |