diff mbox series

[RFC,08/14] efi_loader: capsule: support firmware update

Message ID 20200317021247.5849-9-takahiro.akashi@linaro.org
State New
Headers show
Series efi_loader: add capsule update support | expand

Commit Message

AKASHI Takahiro March 17, 2020, 2:12 a.m. UTC
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, installing drivers is not supported yet.

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 at linaro.org>
---
 include/efi_api.h            | 127 +++++++++++++++++++++++++++
 lib/efi_loader/Kconfig       |  12 +++
 lib/efi_loader/efi_capsule.c | 165 +++++++++++++++++++++++++++++++++++
 lib/efi_loader/efi_setup.c   |   4 +
 4 files changed, 308 insertions(+)

Comments

Sughosh Ganu March 18, 2020, 2:09 p.m. UTC | #1
On Tue, 17 Mar 2020 at 07:42, AKASHI Takahiro <takahiro.akashi at linaro.org>
wrote:

> 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, installing drivers is not supported yet.
>
> 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 at linaro.org>
> ---
>  include/efi_api.h            | 127 +++++++++++++++++++++++++++
>  lib/efi_loader/Kconfig       |  12 +++
>  lib/efi_loader/efi_capsule.c | 165 +++++++++++++++++++++++++++++++++++
>  lib/efi_loader/efi_setup.c   |   4 +
>  4 files changed, 308 insertions(+)
>

<snip>


> diff --git a/lib/efi_loader/efi_capsule.c b/lib/efi_loader/efi_capsule.c
> index f3e2a555a6b9..f3526beed681 100644
> --- a/lib/efi_loader/efi_capsule.c
> +++ b/lib/efi_loader/efi_capsule.c
> @@ -14,10 +14,164 @@
>  #include <sort.h>
>

<snip>


>  /*
>   * Launch a capsule
>   */
> @@ -54,6 +208,17 @@ efi_status_t EFIAPI efi_update_capsule(
>         ret = EFI_SUCCESS;
>         for (i = 0, capsule = *capsule_header_array; i < capsule_count;
>              i++, capsule = *(++capsule_header_array)) {
> +               EFI_PRINT("EFI Capsule (guid:%pUl)\n",
> &capsule->capsule_guid);
> +               if (!guidcmp(&capsule->capsule_guid,
> +                            &efi_guid_firmware_management_capsule_id))
> +                       ret  = efi_capsule_update_firmware(
> +                                       (struct
> efi_firmware_management_capsule_header *)
> +                                       ((void *)capsule +
> sizeof(*capsule)));
>

Instead of sizeof(*capsule), please use header_size member of
efi_capsule_header. The spec mentions that the size of the capsule header
might be larger than the capsule header structure. Moreover, when testing
with a capsule built from the edk2 capsule generation script, i do see that
the header_size is greater than sizeof(efi_capsule_header).

-sughosh
diff mbox series

Patch

diff --git a/include/efi_api.h b/include/efi_api.h
index b7bf21cac7ad..e103369186a2 100644
--- a/include/efi_api.h
+++ b/include/efi_api.h
@@ -225,6 +225,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 +257,32 @@  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;
+} __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
@@ -1683,4 +1713,101 @@  struct efi_unicode_collation_protocol {
 #define LAST_ATTEMPT_STATUS_ERROR_UNSUCCESSFUL_VENDOR_RANGE_MIN 0x00001000
 #define LAST_ATTEMPT_STATUS_ERROR_UNSUCCESSFUL_VENDOR_RANGE_MAX 0x00004000
 
+/*
+ * Firmware management protocol
+ */
+#define EFI_FIRMWARE_MANAGEMENT_PROTOCOL_GUID \
+	EFI_GUID(0x86c77a67, 0x0b97, 0x4633, 0xa1, 0x87, \
+		 0x49, 0x10, 0x4d, 0x06, 0x85, 0xc7)
+
+#define EFI_FIRMWARE_IMAGE_TYPE_UBOOT_FIT_GUID \
+	EFI_GUID(0xae13ff2d, 0x9ad4, 0x4e25, 0x9a, 0xc8, \
+		 0x6d, 0x80, 0xb3, 0xb2, 0x21, 0x47)
+
+#define EFI_IMAGE_ATTRIBUTE_IMAGE_UPDATABLE		0x1
+#define EFI_IMAGE_ATTRIBUTE_RESET_REQUIRED		0x2
+#define EFI_IMAGE_ATTRIBUTE_AUTHENTICATION_REQUIRED	0x4
+#define EFI_IMAGE_ATTRIBUTE_IN_USE			0x8
+#define EFI_IMAGE_ATTRIBUTE_UEFI_IMAGE			0x10
+
+#define EFI_IMAGE_COMPATIBILITY_CHECK_SUPPORTED		0x1
+#define EFI_FIRMWARE_IMAGE_DESCRIPTOR_VERSION		4
+
+#define EFI_IMAGE_UPDATABLE_VALID			0x1
+#define EFI_IMAGE_UPDATABLE_INVALID			0x2
+#define EFI_IMAGE_UPDATABLE_INVALID_TYPE		0x4
+#define EFI_IMAGE_UPDATABLE_INVALID_OLLD		0x8
+#define EFI_IMAGE_UPDATABLE_VALID_WITH_VENDOR_CODE	0x10
+
+#define EFI_PACKAGE_ATTRIBUTE_VERSION_UPDATABLE		0x1
+#define EFI_PACKAGE_ATTRIBUTE_RESET_REQUIRED		0x2
+#define EFI_PACKAGE_ATTRIBUTE_AUTHENTICATION_REQUIRED	0x4
+
+typedef struct efi_firmware_image_dependencies {
+	u8 dependencies[0];
+} efi_fmp_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_fmp_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
diff --git a/lib/efi_loader/Kconfig b/lib/efi_loader/Kconfig
index 95e10f7d981b..43d6f75d557a 100644
--- a/lib/efi_loader/Kconfig
+++ b/lib/efi_loader/Kconfig
@@ -97,6 +97,18 @@  config EFI_CAPSULE_UPDATE
 	  Select this option if you want to use capsule update feature,
 	  including firmware updates and variable updates.
 
+
+if EFI_CAPSULE_UPDATE
+
+config EFI_CAPSULE_UPDATE_FIRMWARE
+	bool "Capsule based firmware update"
+	default n
+	help
+	  Select this option if you want to enable capsule-based
+	  firmware update
+
+endif
+
 config EFI_CAPSULE_ON_DISK
 	bool "Enable capsule-on-disk support"
 	depends on EFI_CAPSULE_UPDATE
diff --git a/lib/efi_loader/efi_capsule.c b/lib/efi_loader/efi_capsule.c
index f3e2a555a6b9..f3526beed681 100644
--- a/lib/efi_loader/efi_capsule.c
+++ b/lib/efi_loader/efi_capsule.c
@@ -14,10 +14,164 @@ 
 #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;
 
 /* for file system access */
 static struct efi_file_handle *bootdev_root;
 
+#ifdef CONFIG_EFI_CAPSULE_UPDATE_FIRMWARE
+/*
+ * Handle firmware management capsules
+ */
+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;
+	bool found = false;
+	int i;
+	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;
+		ret = fmp->get_image_info(fmp, &info_size, image_info,
+				&descriptor_version, &descriptor_count,
+				&descriptor_size, NULL, NULL);
+		if (ret != EFI_BUFFER_TOO_SMALL)
+			goto skip;
+		image_info = malloc(info_size);
+		if (!image_info)
+			goto skip;
+
+		ret = fmp->get_image_info(fmp, &info_size, image_info,
+				&descriptor_version, &descriptor_count,
+				&descriptor_size, NULL, NULL);
+		if (ret != EFI_SUCCESS)
+			goto skip;
+
+		/* matching */
+		for (i = 0, desc = image_info; i < descriptor_count;
+		     i++, desc = (void *)desc + descriptor_size)
+			if (!guidcmp(&desc->image_type_id, image_type) &&
+			    (!instance ||
+			     ((descriptor_version >= 3) &&
+			      (desc->hardware_instance == instance))))
+				found = true;
+
+skip:
+		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;
+}
+
+static efi_status_t efi_capsule_update_firmware(
+		struct efi_firmware_management_capsule_header *capsule)
+{
+	struct efi_firmware_management_capsule_image_header *image;
+	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;
+
+	if (capsule->version != 0x00000001)
+		return EFI_INVALID_PARAMETER;
+
+	/* Drivers */
+	/* TODO: support loading drivers */
+
+	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++) {
+		image = (struct efi_firmware_management_capsule_image_header *)
+			((void *)capsule + capsule->item_offset_list[item]);
+
+		if (image->version != 0x00000001 &&
+		    image->version != 0x00000002 &&
+		    image->version != 0x00000003) {
+			ret = EFI_INVALID_PARAMETER;
+			goto out;
+		}
+
+		/* find a device for update firmware */
+		fmp = efi_fmp_find(&image->update_image_type_id,
+				   image->version == 0x1 ? 0 :
+					image->update_hardware_instance,
+				   handles, no_handles);
+		if (!fmp) {
+			printf("EFI Capsule: not support firmware type: %pUl\n",
+			       &image->update_image_type_id);
+			ret = EFI_UNSUPPORTED;
+			goto out;
+		}
+
+		/* do it */
+		image_binary = (void *)image + sizeof(*image);
+		vendor_code = image_binary + image->update_image_size;
+
+		abort_reason = NULL;
+		ret = fmp->set_image(fmp, image->update_image_index,
+				image_binary, image->update_image_size,
+				vendor_code, NULL, &abort_reason);
+		if (ret != EFI_SUCCESS) {
+			printf("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_firmware_management_capsule_header *capsule)
+{
+	return EFI_UNSUPPORTED;
+}
+#endif /* CONFIG_EFI_CAPSULE_UPDATE_FIRMWARE */
+
 /*
  * Launch a capsule
  */
@@ -54,6 +208,17 @@  efi_status_t EFIAPI efi_update_capsule(
 	ret = EFI_SUCCESS;
 	for (i = 0, capsule = *capsule_header_array; i < capsule_count;
 	     i++, capsule = *(++capsule_header_array)) {
+		EFI_PRINT("EFI Capsule (guid:%pUl)\n", &capsule->capsule_guid);
+		if (!guidcmp(&capsule->capsule_guid,
+			     &efi_guid_firmware_management_capsule_id))
+			ret  = efi_capsule_update_firmware(
+					(struct efi_firmware_management_capsule_header *)
+					((void *)capsule + sizeof(*capsule)));
+		else
+			ret = EFI_UNSUPPORTED;
+
+		if (ret != EFI_SUCCESS)
+			goto out;
 	}
 out:
 	return EFI_EXIT(ret);
diff --git a/lib/efi_loader/efi_setup.c b/lib/efi_loader/efi_setup.c
index 309defc5e40d..ca2d03c44610 100644
--- a/lib/efi_loader/efi_setup.c
+++ b/lib/efi_loader/efi_setup.c
@@ -100,6 +100,10 @@  static efi_status_t efi_init_os_indications(void)
 #ifdef CONFIG_EFI_CAPSULE_ON_DISK
 	os_indications_supported |=
 			EFI_OS_INDICATIONS_FILE_CAPSULE_DELIVERY_SUPPORTED;
+#endif
+#ifdef CONFIG_EFI_CAPSULE_UPDATE_FIRMWARE
+	os_indications_supported |=
+			EFI_OS_INDICATIONS_FMP_CAPSULE_SUPPORTED;
 #endif
 	return EFI_CALL(efi_set_variable(L"OsIndicationsSupported",
 					 &efi_global_variable_guid,