// SPDX-License-Identifier: GPL-2.0-only /* * EFI application ESRT tables support * * Copyright (C) 2021 Arm Ltd. */ #include #include #include #include #include const efi_guid_t efi_esrt_guid = EFI_SYSTEM_RESOURCE_TABLE_GUID; static struct efi_system_resource_table *esrt; #define EFI_ESRT_VERSION 1 /** * efi_esrt_image_info_to_entry() - copy the information present in a fw image * descriptor to a ESRT entry. * The function ensures the ESRT entry matches the image_type_id in @img_info. * In case of a mismatch we leave the entry unchanged. * * @img_info: the source image info descriptor * @entry: pointer to the ESRT entry to be filled * @desc_version: the version of the elements in img_info * @image_type: the image type value to be set in the ESRT entry * @flags: the capsule flags value to be set in the ESRT entry * * Return: * - EFI_SUCCESS if the entry is correctly updated * - EFI_INVALID_PARAMETER if entry does not match image_type_id in @img_info. */ static efi_status_t efi_esrt_image_info_to_entry(struct efi_firmware_image_descriptor *img_info, struct efi_system_resource_entry *entry, u32 desc_version, u32 image_type, u32 flags) { if (guidcmp(&entry->fw_class, &img_info->image_type_id)) { EFI_PRINT("ESRT entry %pUL mismatches img_type_id %pUL\n", &entry->fw_class, &img_info->image_type_id); return EFI_INVALID_PARAMETER; } entry->fw_version = img_info->version; entry->fw_type = image_type; entry->capsule_flags = flags; /* * The field lowest_supported_image_version is only present * on image info structure of version 2 or greater. * See the EFI_FIRMWARE_IMAGE_DESCRIPTOR definition in UEFI. */ if (desc_version >= 2) entry->lowest_supported_fw_version = img_info->lowest_supported_image_version; else entry->lowest_supported_fw_version = 0; /* * The fields last_attempt_version and last_attempt_status * are only present on image info structure of version 3 or * greater. * See the EFI_FIRMWARE_IMAGE_DESCRIPTOR definition in UEFI. */ if (desc_version >= 3) { entry->last_attempt_version = img_info->last_attempt_version; entry->last_attempt_status = img_info->last_attempt_status; } else { entry->last_attempt_version = 0; entry->last_attempt_status = LAST_ATTEMPT_STATUS_SUCCESS; } return EFI_SUCCESS; } /** * efi_esrt_entries_to_size() - Obtain the bytes used by an ESRT * datastructure with @num_entries. * * @num_entries: the number of entries in the ESRT. * * Return: the number of bytes an ESRT with @num_entries occupies in memory. */ static inline u32 efi_esrt_entries_to_size(u32 num_entries) { u32 esrt_size = sizeof(struct efi_system_resource_table) + num_entries * sizeof(struct efi_system_resource_entry); return esrt_size; } /** * efi_esrt_allocate_install() - Allocates @num_entries for the ESRT and * performs basic ESRT initialization. * * @num_entries: the number of entries that the ESRT will hold. * * Return: * - pointer to the ESRT if successful. * - NULL otherwise. */ static efi_status_t efi_esrt_allocate_install(u32 num_entries) { efi_status_t ret; struct efi_system_resource_table *new_esrt; u32 size = efi_esrt_entries_to_size(num_entries); efi_guid_t esrt_guid = efi_esrt_guid; /* Reserve memory for ESRT */ ret = efi_allocate_pool(EFI_BOOT_SERVICES_DATA, size, (void **)&new_esrt); if (ret != EFI_SUCCESS) { EFI_PRINT("ESRT cannot allocate memory for %u entries (%u bytes)\n", num_entries, size); return ret; } new_esrt->fw_resource_count_max = num_entries; new_esrt->fw_resource_count = 0; new_esrt->fw_resource_version = EFI_ESRT_VERSION; /* Install the ESRT in the system configuration table. */ ret = efi_install_configuration_table(&esrt_guid, (void *)new_esrt); if (ret != EFI_SUCCESS) { EFI_PRINT("ESRT failed to install the ESRT in the system table\n"); return ret; } /* If there was a previous ESRT, deallocate its memory now. */ if (esrt) ret = efi_free_pool(esrt); esrt = new_esrt; return EFI_SUCCESS; } /** * esrt_find_entry() - Obtain the ESRT entry for the image with GUID * @img_fw_class. * * If the img_fw_class is not yet present in the ESRT, this function * reserves the tail element of the current ESRT as the entry for that fw_class. * The number of elements in the ESRT is updated in that case. * * @img_fw_class: the GUID of the FW image which ESRT entry we want to obtain. * * Return: * - A pointer to the ESRT entry for the image with GUID img_fw_class, * - NULL if: * - there is no more space in the ESRT, * - ESRT is not initialized, */ static struct efi_system_resource_entry *esrt_find_entry(efi_guid_t *img_fw_class) { u32 filled_entries; u32 max_entries; struct efi_system_resource_entry *entry; if (!esrt) { EFI_PRINT("ESRT access before initialized\n"); return NULL; } filled_entries = esrt->fw_resource_count; entry = esrt->entries; /* Check if the image with img_fw_class is already in the ESRT. */ for (u32 idx = 0; idx < filled_entries; idx++) { if (!guidcmp(&entry[idx].fw_class, img_fw_class)) { EFI_PRINT("ESRT found entry for image %pUs at index %u\n", img_fw_class, idx); return &entry[idx]; } } max_entries = esrt->fw_resource_count_max; /* * Since the image with img_fw_class is not present in the ESRT, check * if ESRT is full before appending the new entry to it. */ if (filled_entries == max_entries) { EFI_PRINT("ESRT full, this should not happen\n"); return NULL; } /* * This is a new entry for a fw image, increment the element * number in the table and set the fw_class field. */ esrt->fw_resource_count++; entry[filled_entries].fw_class = *img_fw_class; EFI_PRINT("ESRT allocated new entry for image %pUs at index %u\n", img_fw_class, filled_entries); return &entry[filled_entries]; } /** * efi_esrt_add_from_fmp() - Populates a sequence of ESRT entries from the FW * images in the FMP. * * @fmp: the FMP instance from which FW images are added to the ESRT * * Return: * - EFI_SUCCESS if all the FW images in the FMP are added to the ESRT * - Error status otherwise */ static efi_status_t efi_esrt_add_from_fmp(struct efi_firmware_management_protocol *fmp) { struct efi_system_resource_entry *entry = NULL; size_t info_size = 0; struct efi_firmware_image_descriptor *img_info = NULL; u32 desc_version; u8 desc_count; size_t desc_size; u32 package_version; u16 *package_version_name; efi_status_t ret = EFI_SUCCESS; /* * TODO: set the field image_type depending on the FW image type * defined in a platform basis. */ u32 image_type = ESRT_FW_TYPE_UNKNOWN; /* TODO: set the capsule flags as a function of the FW image type. */ u32 flags = 0; ret = EFI_CALL(fmp->get_image_info(fmp, &info_size, img_info, &desc_version, &desc_count, &desc_size, NULL, NULL)); if (ret != EFI_BUFFER_TOO_SMALL) { /* * An input of info_size=0 should always lead * fmp->get_image_info to return BUFFER_TO_SMALL. */ EFI_PRINT("Erroneous FMP implementation\n"); return EFI_INVALID_PARAMETER; } ret = efi_allocate_pool(EFI_BOOT_SERVICES_DATA, info_size, (void **)&img_info); if (ret != EFI_SUCCESS) { EFI_PRINT("ESRT failed to allocate memory for image info.\n"); return ret; } ret = EFI_CALL(fmp->get_image_info(fmp, &info_size, img_info, &desc_version, &desc_count, &desc_size, &package_version, &package_version_name)); if (ret != EFI_SUCCESS) { EFI_PRINT("ESRT failed to obtain the FMP image info\n"); goto out; } /* * Iterate over all the FW images in the FMP. */ for (u32 desc_idx = 0; desc_idx < desc_count; desc_idx++) { struct efi_firmware_image_descriptor *cur_img_info = (struct efi_firmware_image_descriptor *) ((uintptr_t)img_info + desc_idx * desc_size); /* * Obtain the ESRT entry for the FW image with fw_class * equal to cur_img_info->image_type_id. */ entry = esrt_find_entry(&cur_img_info->image_type_id); if (entry) { ret = efi_esrt_image_info_to_entry(cur_img_info, entry, desc_version, image_type, flags); if (ret != EFI_SUCCESS) EFI_PRINT("ESRT entry mismatches image_type\n"); } else { EFI_PRINT("ESRT failed to add entry for %pUs\n", &cur_img_info->image_type_id); continue; } } out: efi_free_pool(img_info); return EFI_SUCCESS; } /** * efi_esrt_populate() - Populates the ESRT entries from the FMP instances * present in the system. * If an ESRT already exists, the old ESRT is replaced in the system table. * The memory of the old ESRT is deallocated. * * Return: * - EFI_SUCCESS if the ESRT is correctly created * - error code otherwise. */ efi_status_t efi_esrt_populate(void) { efi_handle_t *base_handle = NULL; efi_handle_t *it_handle; efi_uintn_t no_handles = 0; struct efi_firmware_management_protocol *fmp; efi_status_t ret; u32 num_entries = 0; struct efi_handler *handler; /* * Obtain the number of registered FMP handles. */ ret = EFI_CALL(efi_locate_handle_buffer(BY_PROTOCOL, &efi_guid_firmware_management_protocol, NULL, &no_handles, (efi_handle_t **)&base_handle)); if (ret != EFI_SUCCESS) { EFI_PRINT("ESRT There are no FMP instances\n"); ret = efi_esrt_allocate_install(0); if (ret != EFI_SUCCESS) { EFI_PRINT("ESRT failed to create table with 0 entries\n"); return ret; } return EFI_SUCCESS; } EFI_PRINT("ESRT populate esrt from (%zd) available FMP handles\n", no_handles); /* * Iterate over all FMPs to determine an upper bound on the number of * ESRT entries. */ it_handle = base_handle; for (u32 idx = 0; idx < no_handles; idx++, it_handle++) { struct efi_firmware_image_descriptor *img_info = NULL; size_t info_size = 0; u32 desc_version = 0; u8 desc_count = 0; size_t desc_size = 0; u32 package_version; u16 *package_version_name; ret = efi_search_protocol(*it_handle, &efi_guid_firmware_management_protocol, &handler); if (ret != EFI_SUCCESS) { EFI_PRINT("ESRT Unable to find FMP handle (%u)\n", idx); goto out; } fmp = handler->protocol_interface; ret = EFI_CALL(fmp->get_image_info(fmp, &info_size, NULL, &desc_version, &desc_count, &desc_size, &package_version, &package_version_name)); if (ret != EFI_BUFFER_TOO_SMALL) { /* * An input of info_size=0 should always lead * fmp->get_image_info to return BUFFER_TO_SMALL. */ EFI_PRINT("ESRT erroneous FMP implementation\n"); ret = EFI_INVALID_PARAMETER; goto out; } ret = efi_allocate_pool(EFI_BOOT_SERVICES_DATA, info_size, (void **)&img_info); if (ret != EFI_SUCCESS) { EFI_PRINT("ESRT failed to allocate memory for image info\n"); goto out; } /* * Calls to a FMP get_image_info method do not return the * desc_count value if the return status differs from EFI_SUCCESS. * We need to repeat the call to get_image_info with a properly * sized buffer in order to obtain the real number of images * handled by the FMP. */ ret = EFI_CALL(fmp->get_image_info(fmp, &info_size, img_info, &desc_version, &desc_count, &desc_size, &package_version, &package_version_name)); if (ret != EFI_SUCCESS) { EFI_PRINT("ESRT failed to obtain image info from FMP\n"); efi_free_pool(img_info); goto out; } num_entries += desc_count; efi_free_pool(img_info); } EFI_PRINT("ESRT create table with %u entries\n", num_entries); /* * Allocate an ESRT with the sufficient number of entries to accommodate * all the FMPs in the system. */ ret = efi_esrt_allocate_install(num_entries); if (ret != EFI_SUCCESS) { EFI_PRINT("ESRT failed to initialize table\n"); goto out; } /* * Populate the ESRT entries with all existing FMP. */ it_handle = base_handle; for (u32 idx = 0; idx < no_handles; idx++, it_handle++) { ret = efi_search_protocol(*it_handle, &efi_guid_firmware_management_protocol, &handler); if (ret != EFI_SUCCESS) { EFI_PRINT("ESRT unable to find FMP handle (%u)\n", idx); break; } fmp = handler->protocol_interface; ret = efi_esrt_add_from_fmp(fmp); if (ret != EFI_SUCCESS) EFI_PRINT("ESRT failed to add FMP to the table\n"); } out: efi_free_pool(base_handle); return ret; } /** * efi_esrt_new_fmp_notify() - Callback for the EVT_NOTIFY_SIGNAL event raised * when a new FMP protocol instance is registered in the system. */ static void EFIAPI efi_esrt_new_fmp_notify(struct efi_event *event, void *context) { efi_status_t ret; EFI_ENTRY(); ret = efi_esrt_populate(); if (ret != EFI_SUCCESS) EFI_PRINT("ESRT failed to populate ESRT entry\n"); EFI_EXIT(ret); } /** * efi_esrt_register() - Install the ESRT system table. * * Return: status code */ efi_status_t efi_esrt_register(void) { struct efi_event *ev = NULL; void *registration; efi_status_t ret; EFI_PRINT("ESRT creation start\n"); ret = efi_esrt_populate(); if (ret != EFI_SUCCESS) { EFI_PRINT("ESRT failed to initiate the table\n"); return ret; } ret = efi_create_event(EVT_NOTIFY_SIGNAL, TPL_CALLBACK, efi_esrt_new_fmp_notify, NULL, NULL, &ev); if (ret != EFI_SUCCESS) { EFI_PRINT("ESRT failed to create event\n"); return ret; } ret = EFI_CALL(efi_register_protocol_notify(&efi_guid_firmware_management_protocol, ev, ®istration)); if (ret != EFI_SUCCESS) { EFI_PRINT("ESRT failed to register FMP callback\n"); return ret; } EFI_PRINT("ESRT table created\n"); return ret; }