]> git.dujemihanovic.xyz Git - u-boot.git/commitdiff
efi_loader: capsule: support firmware update
authorAKASHI Takahiro <takahiro.akashi@linaro.org>
Mon, 30 Nov 2020 09:12:11 +0000 (18:12 +0900)
committerHeinrich Schuchardt <xypron.glpk@gmx.de>
Thu, 3 Dec 2020 20:22:50 +0000 (21:22 +0100)
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>
include/efi_api.h
include/efi_loader.h
lib/efi_loader/Kconfig
lib/efi_loader/efi_capsule.c
lib/efi_loader/efi_setup.c

index fadf40e8ff3baf4510f0a654e764de8fcb3346fc..5d2f7bbbe326b70e3bf1835179eef870a63d70fc 100644 (file)
@@ -217,6 +217,9 @@ enum efi_reset_type {
 #define CAPSULE_FLAGS_POPULATE_SYSTEM_TABLE    0x00020000
 #define CAPSULE_FLAGS_INITIATE_RESET           0x00040000
 
+#define CAPSULE_SUPPORT_AUTHENTICATION         0x0000000000000001
+#define CAPSULE_SUPPORT_DEPENDENCY             0x0000000000000002
+
 #define EFI_CAPSULE_REPORT_GUID \
        EFI_GUID(0x39b68c46, 0xf7fb, 0x441b, 0xb6, 0xec, \
                 0x16, 0xb0, 0xf6, 0x98, 0x21, 0xf3)
@@ -225,6 +228,10 @@ enum efi_reset_type {
        EFI_GUID(0xde9f0ec, 0x88b6, 0x428f, 0x97, 0x7a, \
                 0x25, 0x8f, 0x1d, 0xe, 0x5e, 0x72)
 
+#define EFI_FIRMWARE_MANAGEMENT_CAPSULE_ID_GUID \
+       EFI_GUID(0x6dcbd5ed, 0xe82d, 0x4c44, 0xbd, 0xa1, \
+                0x71, 0x94, 0x19, 0x9a, 0xd9, 0x2a)
+
 struct efi_capsule_header {
        efi_guid_t capsule_guid;
        u32 header_size;
@@ -253,6 +260,33 @@ struct efi_memory_range_capsule {
        struct efi_memory_range memory_ranges[];
 } __packed;
 
+struct efi_firmware_management_capsule_header {
+       u32 version;
+       u16 embedded_driver_count;
+       u16 payload_item_count;
+       u64 item_offset_list[];
+} __packed;
+
+struct efi_firmware_management_capsule_image_header {
+       u32 version;
+       efi_guid_t update_image_type_id;
+       u8 update_image_index;
+       u8 reserved[3];
+       u32 update_image_size;
+       u32 update_vendor_code_size;
+       u64 update_hardware_instance;
+       u64 image_capsule_support;
+} __packed;
+
+struct efi_capsule_result_variable_fmp {
+       u16 version;
+       u8 payload_index;
+       u8 update_image_index;
+       efi_guid_t update_image_type_id;
+       // u16 capsule_file_name[];
+       // u16 capsule_target[];
+} __packed;
+
 #define EFI_RT_SUPPORTED_GET_TIME                      0x0001
 #define EFI_RT_SUPPORTED_SET_TIME                      0x0002
 #define EFI_RT_SUPPORTED_GET_WAKEUP_TIME               0x0004
@@ -1812,4 +1846,99 @@ struct efi_signature_list {
 /*     struct efi_signature_data signatures[...][signature_size]; */
 } __attribute__((__packed__));
 
+/*
+ * Firmware management protocol
+ */
+#define EFI_FIRMWARE_MANAGEMENT_PROTOCOL_GUID \
+       EFI_GUID(0x86c77a67, 0x0b97, 0x4633, 0xa1, 0x87, \
+                0x49, 0x10, 0x4d, 0x06, 0x85, 0xc7)
+
+#define IMAGE_ATTRIBUTE_IMAGE_UPDATABLE                0x0000000000000001
+#define IMAGE_ATTRIBUTE_RESET_REQUIRED         0x0000000000000002
+#define IMAGE_ATTRIBUTE_AUTHENTICATION_REQUIRED        0x0000000000000004
+#define IMAGE_ATTRIBUTE_IN_USE                 0x0000000000000008
+#define IMAGE_ATTRIBUTE_UEFI_IMAGE             0x0000000000000010
+#define IMAGE_ATTRIBUTE_DEPENDENCY             0x0000000000000020
+
+#define IMAGE_COMPATIBILITY_CHECK_SUPPORTED    0x0000000000000001
+
+#define IMAGE_UPDATABLE_VALID                  0x0000000000000001
+#define IMAGE_UPDATABLE_INVALID                        0x0000000000000002
+#define IMAGE_UPDATABLE_INVALID_TYPE           0x0000000000000004
+#define IMAGE_UPDATABLE_INVALID_OLLD           0x0000000000000008
+#define IMAGE_UPDATABLE_VALID_WITH_VENDOR_CODE 0x0000000000000010
+
+#define PACKAGE_ATTRIBUTE_VERSION_UPDATABLE            0x0000000000000001
+#define PACKAGE_ATTRIBUTE_RESET_REQUIRED               0x0000000000000002
+#define PACKAGE_ATTRIBUTE_AUTHENTICATION_REQUIRED      0x0000000000000004
+
+#define EFI_FIRMWARE_IMAGE_DESCRIPTOR_VERSION  4
+
+typedef struct efi_firmware_image_dependencies {
+       u8 dependencies[0];
+} efi_firmware_image_dep_t;
+
+struct efi_firmware_image_descriptor {
+       u8 image_index;
+       efi_guid_t image_type_id;
+       u64 image_id;
+       u16 *image_id_name;
+       u32 version;
+       u16 *version_name;
+       efi_uintn_t size;
+       u64 attributes_supported;
+       u64 attributes_setting;
+       u64 compatibilities;
+       u32 lowest_supported_image_version;
+       u32 last_attempt_version;
+       u32 last_attempt_status;
+       u64 hardware_instance;
+       efi_firmware_image_dep_t *dependencies;
+};
+
+struct efi_firmware_management_protocol {
+       efi_status_t (EFIAPI *get_image_info)(
+                       struct efi_firmware_management_protocol *this,
+                       efi_uintn_t *image_info_size,
+                       struct efi_firmware_image_descriptor *image_info,
+                       u32 *descriptor_version,
+                       u8 *descriptor_count,
+                       efi_uintn_t *descriptor_size,
+                       u32 *package_version,
+                       u16 **package_version_name);
+       efi_status_t (EFIAPI *get_image)(
+                       struct efi_firmware_management_protocol *this,
+                       u8 image_index,
+                       void *image,
+                       efi_uintn_t *image_size);
+       efi_status_t (EFIAPI *set_image)(
+                       struct efi_firmware_management_protocol *this,
+                       u8 image_index,
+                       const void *image,
+                       efi_uintn_t image_size,
+                       const void *vendor_code,
+                       efi_status_t (*progress)(efi_uintn_t completion),
+                       u16 **abort_reason);
+       efi_status_t (EFIAPI *check_image)(
+                       struct efi_firmware_management_protocol *this,
+                       u8 image_index,
+                       const void *image,
+                       efi_uintn_t *image_size,
+                       u32 *image_updatable);
+       efi_status_t (EFIAPI *get_package_info)(
+                       struct efi_firmware_management_protocol *this,
+                       u32 *package_version,
+                       u16 **package_version_name,
+                       u32 *package_version_name_maxlen,
+                       u64 *attributes_supported,
+                       u64 *attributes_setting);
+       efi_status_t (EFIAPI *set_package_info)(
+                       struct efi_firmware_management_protocol *this,
+                       const void *image,
+                       efi_uintn_t *image_size,
+                       const void *vendor_code,
+                       u32 package_version,
+                       const u16 *package_version_name);
+};
+
 #endif
index de1d24b3334a2ab0dda08763570f214aef387db7..ed5a49b520dcd5d7b5d7ba866aa4c3a2e3d2e7e3 100644 (file)
@@ -212,6 +212,8 @@ extern const efi_guid_t efi_guid_cert_type_pkcs7;
 extern const efi_guid_t efi_guid_rng_protocol;
 /* GUID of capsule update result */
 extern const efi_guid_t efi_guid_capsule_report;
+/* GUID of firmware management protocol */
+extern const efi_guid_t efi_guid_firmware_management_protocol;
 
 extern unsigned int __efi_runtime_start, __efi_runtime_stop;
 extern unsigned int __efi_runtime_rel_start, __efi_runtime_rel_stop;
index 23ba065e04a5e11101ce6917af0e20b997d98e0b..13e793014dcdcfda44ade1f0d1d3fe0fed6db972 100644 (file)
@@ -127,6 +127,14 @@ config EFI_CAPSULE_ON_DISK_EARLY
          executed as part of U-Boot initialisation so that they will
          surely take place whatever is set to distro_bootcmd.
 
+config EFI_CAPSULE_FIRMWARE_MANAGEMENT
+       bool "Capsule: Firmware Management Protocol"
+       depends on EFI_HAVE_CAPSULE_SUPPORT
+       default y
+       help
+         Select this option if you want to enable capsule-based
+         firmware update using Firmware Management Protocol.
+
 config EFI_DEVICE_PATH_TO_TEXT
        bool "Device path to text protocol"
        default y
index b3c7d1b735b686e45a7641a68368bb6b1a7f41e7..3e7ad470d484cf27080b1bddd0eae2f6b9a72b14 100644 (file)
 #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();
 
index a126a59f18c1833da1d19dfc36dfb458ff847523..ce6292f559b18079759e9b30bc87fcc459feac7e 100644 (file)
@@ -159,6 +159,10 @@ static efi_status_t efi_init_os_indications(void)
                os_indications_supported |=
                        EFI_OS_INDICATIONS_FILE_CAPSULE_DELIVERY_SUPPORTED;
 
+       if (IS_ENABLED(CONFIG_EFI_CAPSULE_FIRMWARE_MANAGEMENT))
+               os_indications_supported |=
+                       EFI_OS_INDICATIONS_FMP_CAPSULE_SUPPORTED;
+
        return efi_set_variable_int(L"OsIndicationsSupported",
                                    &efi_global_variable_guid,
                                    EFI_VARIABLE_BOOTSERVICE_ACCESS |