On 28.01.21 14:29, Jose Marinho wrote: > The ESRT is initialised during efi_init_objlist, after > efi_initialize_system_table(). > > In this commit, the ESRT occupies at most one page. > During initialization, the list of all the already existing > FMP instances is traversed in order to fill the corresponsing entries. > > Later, when any FMP is added, the corresponding ESRT entries are > initialized as long as there is available entries in the table. > > Signed-off-by: Jose Marinho <jose.mari...@arm.com> > CC: Heinrich Schuchardt <xypron.g...@gmx.de> > CC: Sughosh Ganu <sughosh.g...@linaro.org> > CC: AKASHI Takahiro <takahiro.aka...@linaro.org> > CC: Andre Przywara <andre.przyw...@arm.com> > CC: Alexander Graf <ag...@csgraf.de> > CC: n...@arm.com > --- > include/efi_api.h | 21 +++ > include/efi_loader.h | 22 +++ > lib/efi_loader/Makefile | 1 + > lib/efi_loader/efi_boottime.c | 12 ++ > lib/efi_loader/efi_esrt.c | 294 ++++++++++++++++++++++++++++++++++ > lib/efi_loader/efi_setup.c | 4 + > 6 files changed, 354 insertions(+) > create mode 100644 lib/efi_loader/efi_esrt.c > > diff --git a/include/efi_api.h b/include/efi_api.h > index 48e48a6263..f4a88603e9 100644 > --- a/include/efi_api.h > +++ b/include/efi_api.h > @@ -1722,6 +1722,23 @@ struct efi_load_file_protocol { > void *buffer); > }; > > +struct efi_system_resource_entry { > + efi_guid_t fw_class; > + uint32_t fw_type; > + uint32_t fw_version; > + uint32_t lowest_supported_fw_version; > + uint32_t capsule_flags; > + uint32_t last_attempt_version; > + uint32_t last_attempt_status; > +} __attribute__((__packed__)); > + > +struct efi_system_resource_table { > + uint32_t fw_resource_count; > + uint32_t fw_resource_count_max; > + uint64_t fw_resource_version; > + struct efi_system_resource_entry entries[]; > +} __attribute__((__packed__)); > + > /* Boot manager load options */ > #define LOAD_OPTION_ACTIVE 0x00000001 > #define LOAD_OPTION_FORCE_RECONNECT 0x00000002 > @@ -1740,6 +1757,10 @@ struct efi_load_file_protocol { > #define ESRT_FW_TYPE_DEVICEFIRMWARE 0x00000002 > #define ESRT_FW_TYPE_UEFIDRIVER 0x00000003 > > +#define EFI_SYSTEM_RESOURCE_TABLE_GUID\ > + EFI_GUID(0xb122a263, 0x3661, 0x4f68,\ > + 0x99, 0x29, 0x78, 0xf8, 0xb0, 0xd6, 0x21, 0x80 )
Dear Joel, thank you for addressing this missing piece in U-Boot. Please, add the GUID to guid_list[] in cmd/efidebug.c. > + > /* Last Attempt Status Values */ > #define LAST_ATTEMPT_STATUS_SUCCESS 0x00000000 > #define LAST_ATTEMPT_STATUS_ERROR_UNSUCCESSFUL 0x00000001 > diff --git a/include/efi_loader.h b/include/efi_loader.h > index f470bbd636..7b20ec1ad2 100644 > --- a/include/efi_loader.h > +++ b/include/efi_loader.h > @@ -884,4 +884,26 @@ static inline efi_status_t efi_launch_capsules(void) > > #endif /* CONFIG_IS_ENABLED(EFI_LOADER) */ > > +/* > + * Install the ESRT system table. > + * > + * @return status code > + */ > +efi_status_t efi_esrt_register(void); > + > +/** > + * esrt_add_from_fmp() - Populates a sequence of ESRT entries from the FW > + * images in the FMP. > + * > + * @fmp: the fmp from which fw images are added to the ESRT > + * @image_type: the type of the FW image in the ESRT entry > + * @flags: the capsule flags to be set in the ESRT entry > + * > + * Return: > + * - EFI_SUCCESS if all the FW images in the FMP are added to the ESRT > + * - Error status otherwise > + */ > +efi_status_t esrt_add_from_fmp(struct efi_firmware_management_protocol *fmp, > + u32 image_type, u32 flags); > + > #endif /* _EFI_LOADER_H */ > diff --git a/lib/efi_loader/Makefile b/lib/efi_loader/Makefile > index 10b42e8847..dec791b310 100644 > --- a/lib/efi_loader/Makefile > +++ b/lib/efi_loader/Makefile > @@ -52,6 +52,7 @@ obj-y += efi_variable.o > obj-$(CONFIG_EFI_VARIABLES_PRESEED) += efi_var_seed.o > endif > obj-y += efi_watchdog.o > +obj-y += efi_esrt.o > obj-$(CONFIG_LCD) += efi_gop.o > obj-$(CONFIG_DM_VIDEO) += efi_gop.o > obj-$(CONFIG_PARTITIONS) += efi_disk.o > diff --git a/lib/efi_loader/efi_boottime.c b/lib/efi_loader/efi_boottime.c > index ce658a8e73..8077b467c7 100644 > --- a/lib/efi_loader/efi_boottime.c > +++ b/lib/efi_loader/efi_boottime.c > @@ -1150,6 +1150,18 @@ efi_status_t efi_add_protocol(const efi_handle_t > handle, > > if (!guidcmp(&efi_guid_device_path, protocol)) > EFI_PRINT("installed device path '%pD'\n", protocol_interface); > + > + if (!guidcmp(&efi_guid_firmware_management_protocol, protocol)) { > + EFI_PRINT("installed FMP, updating ESRT entries\n"); > + > + ret = esrt_add_from_fmp((struct > efi_firmware_management_protocol *) > + protocol_interface, ESRT_FW_TYPE_UNKNOWN, 0); > + if (ret != EFI_SUCCESS) { > + EFI_PRINT("Failed to update FMP ESRT entries\n"); > + return ret; > + } On some systems we are very tight on memory. So we should have a configuration setting to disable the ESRT code. Calling RegisterProtocolNotify() offers a mor UEFI style implementation alternative but would need more code. > + } > + > return EFI_SUCCESS; > } > > diff --git a/lib/efi_loader/efi_esrt.c b/lib/efi_loader/efi_esrt.c > new file mode 100644 > index 0000000000..c651ce6c73 > --- /dev/null > +++ b/lib/efi_loader/efi_esrt.c > @@ -0,0 +1,294 @@ > +// SPDX-License-Identifier: GPL-2.0-only > +/* > + * EFI application ESRT tables support > + * > + * Copyright (C) 2021 Arm Ltd. > + */ > + > +#include <common.h> > +#include <efi_loader.h> > +#include <log.h> > +#include <efi_api.h> > +#include <malloc.h> > + > +static const efi_guid_t esrt_guid = EFI_SYSTEM_RESOURCE_TABLE_GUID; > +static bool esrt_initialized; > +static struct efi_system_resource_table *esrt; > + > +#define EFI_ESRT_NUM_PAGES 1 > + > +#define EFI_ESRT_SIZE (EFI_ESRT_NUM_PAGES * EFI_PAGE_SIZE) > + > +#define EFI_ESRT_ENTRIES_MAX ((EFI_ESRT_SIZE - \ > + sizeof(struct efi_system_resource_table)) / \ > + sizeof(struct efi_system_resource_entry)) > + > +#define EFI_ESRT_VERSION 1 > + > +/** > + * esrt_image_info_to_entry() - copy the information present in a fw image > + * descriptor to a ESRT entry. > + * > + * @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 > + * > + */ > +void > +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) > +{ > + guidcpy(&entry->fw_class, &img_info->image_type_id); > + 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 = EFI_SUCCESS; > + } > +} > + > +/** > + * esrt_find_entry() - Obtain the ESRT entry for the image with guid %s/guid/GUID/ > + * img_fw_class. %s/img_fw_class/@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_clas. > + * The number of elements in the ESRT is updated in that case. > + * > + * @fw_img_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. > + */ > +static > +struct efi_system_resource_entry *esrt_find_entry(efi_guid_t *img_fw_class) > +{ > + // if this function is called before the esrt init then it is erroneous > + // behaviour. > + assert(esrt_initialized); assert is a nop if _DEBUG = 0. Why don't you return NULL in this case? Can this really occur? > + > + const u32 filled_entries = esrt->fw_resource_count; > + const u32 max_entries = esrt->fw_resource_count_max; > + struct efi_system_resource_entry *entry = esrt->entries; > + > + for (u32 idx; idx < filled_entries; idx++) { > + if (!guidcmp(&entry[idx].fw_class, img_fw_class)) { > + log_debug("EFI ESRT: found entry for image %pUl\n", > + img_fw_class); > + return &entry[idx]; > + } > + } > + > + if (filled_entries == max_entries) { > + log_warning("EFI ESRT: table is full\n"); > + return NULL; > + > + } else { > + /* > + * 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; > + return &entry[filled_entries]; > + } > +} > + > +/** > + * esrt_add_from_fmp() - Populates a sequence of ESRT entries from the FW > + * images in the FMP. > + * > + * @fmp: the fmp from which fw images are added to the ESRT > + * @image_type: the type of the FW image in the ESRT entry > + * @flags: the capsule flags to be set in the ESRT entry > + * > + * Return: > + * - EFI_SUCCESS if all the FW images in the FMP are added to the ESRT > + * - Error status otherwise > + */ > +efi_status_t esrt_add_from_fmp(struct efi_firmware_management_protocol *fmp, > + u32 image_type, u32 flags) > +{ > + 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; > + efi_status_t ret = EFI_SUCCESS; > + > + if (!esrt_initialized) > + return EFI_NOT_READY; Can this really occur? > + > + ret = fmp->get_image_info(fmp, &info_size, img_info, > + &desc_version, &desc_count, > + &desc_size, NULL, NULL); > + > + /* > + * An input of info_size=0 should always lead to BUFFER_TO_SMALL return > + * if this is not the case then the get_image_info implementation is > + * anoumalous, hence the assert. %s/anoumalous/anomalous/ > + */ > + assert_noisy(ret == EFI_BUFFER_TOO_SMALL); What do you want the user to do when he sees this message? Why would you continue. I guess the right thing is to return an error EFI_INVALID_PARAMETER if the fmp protocol is not compliant. You can use EFI_PRINT() to write a debug message. > + > + img_info = malloc(info_size); Always check the result of malloc(). > + > + ret = fmp->get_image_info(fmp, &info_size, img_info, > + &desc_version, &desc_count, > + &desc_size, NULL, NULL); > + if (ret != EFI_SUCCESS) { > + log_err("EFI 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) { > + esrt_image_info_to_entry(cur_img_info, entry, > + desc_version, image_type, > + flags); > + } else { > + log_err("EFI ESRT: failed to add esrt entry for %pUl\n", > + &cur_img_info->image_type_id); > + continue; > + } > + } > + > +out: > + free(img_info); > + return true; EFI_SUCCESS > +} > + > +/** > + * esrt_populate() - Populates the ESRT entries from the FMP instances > + * registered so far and performs basic initialization of the ESRT fields. > + * > + * Return: > + * - true if ESRT initialized successfully. > + * - false otherwise. > + */ > +static bool esrt_populate(void) > +{ > + efi_handle_t *handle = NULL; > + size_t no_handles = 0; > + struct efi_firmware_management_protocol *fmp; > + efi_status_t ret; > + > + esrt->fw_resource_count_max = EFI_ESRT_ENTRIES_MAX; > + esrt->fw_resource_version = EFI_ESRT_VERSION; > + > + esrt_initialized = true; > + > + /* > + * 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 **)&handle)); > + > + if (ret != EFI_SUCCESS) { > + log_warning("EFI ESRT: failed to obtain FMP handles (%lx)\n", > + ret); > + return esrt_initialized; > + } Can't we initialize ESRT before any FMP is installed to get rid of this function module? > + > + log_debug("EFI ESRT: populate esrt from (%ld) avaialble FMP handles\n", > + no_handles); > + /* > + * Populate the ESRT entries with any already existing FMP. > + */ > + for (u32 idx = 0; idx < no_handles; idx++, handle++) { > + ret = EFI_CALL(efi_handle_protocol(*handle, > &efi_guid_firmware_management_protocol, > + (void **)&fmp)); > + if (ret != EFI_SUCCESS) { > + log_err("EFI ESRT: Unable to find FMP handle (%d)\n", > + idx); > + continue; > + } > + > + ret = esrt_add_from_fmp(fmp, ESRT_FW_TYPE_UNKNOWN, 0); > + if (ret != EFI_SUCCESS) { > + log_err("EFI ESRT: failed to add from FMP (%d)\n", idx); > + continue; > + } > + } > + return esrt_initialized; > +} > + > +/** > + * efi_esrt_register() - Install the ESRT system table. > + * > + * @return status code > + */ > +efi_status_t efi_esrt_register(void) > +{ > + esrt = (struct efi_system_resource_table *)(uintptr_t)U32_MAX; > + efi_status_t ret; > + > + log_debug("EFI ESRT: ESRT creation start\n"); > + > + /* Reserve pages for ESRT */ > + ret = efi_allocate_pages(EFI_ALLOCATE_MAX_ADDRESS, > + EFI_RUNTIME_SERVICES_DATA, EFI_ESRT_NUM_PAGES, Do we need an ESRT before the installation of the first FMP? Why are you using a fixed number of pages? Doesn't the size depend on the number of FMPs? Shouldn't the table be resized whenever a new protocol is installed? Unfortunately you never check if you overrun the allocated space. So I just need an EFI driver that installs a high number of FMP and it will crash U-Boot. So I would drop this function and move (re-)allocating the ESRT to esrt_add_from_fmp(). Best regards Heinrich > + (uint64_t *)&esrt); > + > + if (ret != EFI_SUCCESS) > + return ret; > + > + if (!esrt_populate()) > + log_warning("EFI ESRT: failed to initialize ESRT\n"); > + > + /* And expose them to our EFI payload */ > + ret = efi_install_configuration_table(&esrt_guid, (void *)esrt); > + if (ret != EFI_SUCCESS) { > + log_err("EFI ESRT: failed to create ESRT\n"); > + return ret; > + } > + > + log_debug("EFI ESRT: table created\n"); > + > + return ret; > +} > diff --git a/lib/efi_loader/efi_setup.c b/lib/efi_loader/efi_setup.c > index 5800cbf6d4..7660a20822 100644 > --- a/lib/efi_loader/efi_setup.c > +++ b/lib/efi_loader/efi_setup.c > @@ -231,6 +231,10 @@ efi_status_t efi_init_obj_list(void) > if (ret != EFI_SUCCESS) > goto out; > > + ret = efi_esrt_register(); > + if (ret != EFI_SUCCESS) > + goto out; > + > if (IS_ENABLED(CONFIG_EFI_TCG2_PROTOCOL)) { > ret = efi_tcg2_register(); > if (ret != EFI_SUCCESS) >