mirror of
				https://github.com/smaeul/u-boot.git
				synced 2025-10-31 20:18:18 +00:00 
			
		
		
		
	Transition the platform to either Trial State or Regular State on a successful update. Do this by calling the fwu_state_machine_updates() API function. For version 1 of the FWU metadata, the transition to Trial State is done by starting the Trial State counter, while for version 2, in addition to the counter, the bank_state field of the FWU metadata is also updated to Valid. For transitioning the platform to Regular State, no action is needed with version 1 of the FWU metadata structure, while for version 2, the bank_state field is set to Accepted. Signed-off-by: Sughosh Ganu <sughosh.ganu@linaro.org> Tested-by: Michal Simek <michal.simek@amd.com>
		
			
				
	
	
		
			1396 lines
		
	
	
		
			35 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			1396 lines
		
	
	
		
			35 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| // SPDX-License-Identifier: GPL-2.0+
 | |
| /*
 | |
|  *  EFI Capsule
 | |
|  *
 | |
|  *  Copyright (c) 2018 Linaro Limited
 | |
|  *			Author: AKASHI Takahiro
 | |
|  */
 | |
| 
 | |
| #define LOG_CATEGORY LOGC_EFI
 | |
| 
 | |
| #include <efi_loader.h>
 | |
| #include <efi_variable.h>
 | |
| #include <env.h>
 | |
| #include <fdtdec.h>
 | |
| #include <fs.h>
 | |
| #include <fwu.h>
 | |
| #include <hang.h>
 | |
| #include <malloc.h>
 | |
| #include <mapmem.h>
 | |
| #include <sort.h>
 | |
| #include <sysreset.h>
 | |
| #include <asm/global_data.h>
 | |
| 
 | |
| #include <crypto/pkcs7.h>
 | |
| #include <crypto/pkcs7_parser.h>
 | |
| #include <linux/err.h>
 | |
| 
 | |
| DECLARE_GLOBAL_DATA_PTR;
 | |
| 
 | |
| 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;
 | |
| const efi_guid_t fwu_guid_os_request_fw_revert =
 | |
| 		FWU_OS_REQUEST_FW_REVERT_GUID;
 | |
| const efi_guid_t fwu_guid_os_request_fw_accept =
 | |
| 		FWU_OS_REQUEST_FW_ACCEPT_GUID;
 | |
| 
 | |
| #define FW_ACCEPT_OS	(u32)0x8000
 | |
| 
 | |
| #ifdef CONFIG_EFI_CAPSULE_ON_DISK
 | |
| /* for file system access */
 | |
| static struct efi_file_handle *bootdev_root;
 | |
| #endif
 | |
| 
 | |
| static __maybe_unused unsigned int get_capsule_index(const u16 *variable_name)
 | |
| {
 | |
| 	u16 value16[11]; /* "CapsuleXXXX": non-null-terminated */
 | |
| 	char value[5];
 | |
| 	efi_uintn_t size;
 | |
| 	unsigned long index = 0xffff;
 | |
| 	efi_status_t ret;
 | |
| 	int i;
 | |
| 
 | |
| 	size = sizeof(value16);
 | |
| 	ret = efi_get_variable_int(variable_name, &efi_guid_capsule_report,
 | |
| 				   NULL, &size, value16, NULL);
 | |
| 	if (ret != EFI_SUCCESS || size != 22 ||
 | |
| 	    u16_strncmp(value16, u"Capsule", 7))
 | |
| 		goto err;
 | |
| 	for (i = 0; i < 4; ++i) {
 | |
| 		u16 c = value16[i + 7];
 | |
| 
 | |
| 		if (!c || c > 0x7f)
 | |
| 			goto err;
 | |
| 		value[i] = c;
 | |
| 	}
 | |
| 	value[4] = 0;
 | |
| 	if (strict_strtoul(value, 16, &index))
 | |
| 		index = 0xffff;
 | |
| err:
 | |
| 	return index;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * get_last_capsule - get the last capsule index
 | |
|  *
 | |
|  * Retrieve the index of the capsule invoked last time from "CapsuleLast"
 | |
|  * variable.
 | |
|  *
 | |
|  * Return:
 | |
|  * * > 0	- the last capsule index invoked
 | |
|  * * 0xffff	- on error, or no capsule invoked yet
 | |
|  */
 | |
| static __maybe_unused unsigned int get_last_capsule(void)
 | |
| {
 | |
| 	return get_capsule_index(u"CapsuleLast");
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * get_max_capsule - get the max capsule index
 | |
|  *
 | |
|  * Retrieve the max capsule index value from "CapsuleMax" variable.
 | |
|  *
 | |
|  * Return:
 | |
|  * * > 0	- the max capsule index
 | |
|  * * 0xffff	- on error, or "CapsuleMax" variable does not exist
 | |
|  */
 | |
| static __maybe_unused unsigned int get_max_capsule(void)
 | |
| {
 | |
| 	return get_capsule_index(u"CapsuleMax");
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * set_capsule_result - set a result variable
 | |
|  * @capsule:		Capsule
 | |
|  * @return_status:	Return status
 | |
|  *
 | |
|  * Create and set a result variable, "CapsuleXXXX", for the capsule,
 | |
|  * @capsule.
 | |
|  */
 | |
| static __maybe_unused
 | |
| void set_capsule_result(int index, struct efi_capsule_header *capsule,
 | |
| 			efi_status_t return_status)
 | |
| {
 | |
| 	u16 variable_name16[12];
 | |
| 	struct efi_capsule_result_variable_header result;
 | |
| 	struct efi_time time;
 | |
| 	efi_status_t ret;
 | |
| 
 | |
| 	efi_create_indexed_name(variable_name16, sizeof(variable_name16),
 | |
| 				"Capsule", index);
 | |
| 	result.variable_total_size = sizeof(result);
 | |
| 	result.capsule_guid = capsule->capsule_guid;
 | |
| 	ret = EFI_CALL((*efi_runtime_services.get_time)(&time, NULL));
 | |
| 	if (ret == EFI_SUCCESS)
 | |
| 		memcpy(&result.capsule_processed, &time, sizeof(time));
 | |
| 	else
 | |
| 		memset(&result.capsule_processed, 0, sizeof(time));
 | |
| 	result.capsule_status = return_status;
 | |
| 	ret = efi_set_variable_int(variable_name16, &efi_guid_capsule_report,
 | |
| 				   EFI_VARIABLE_NON_VOLATILE |
 | |
| 				   EFI_VARIABLE_BOOTSERVICE_ACCESS |
 | |
| 				   EFI_VARIABLE_RUNTIME_ACCESS,
 | |
| 				   sizeof(result), &result, false);
 | |
| 	if (ret != EFI_SUCCESS) {
 | |
| 		log_err("Setting %ls failed\n", variable_name16);
 | |
| 		return;
 | |
| 	}
 | |
| 
 | |
| 	/* Variable CapsuleLast must not include terminating 0x0000 */
 | |
| 	ret = efi_set_variable_int(u"CapsuleLast", &efi_guid_capsule_report,
 | |
| 				   EFI_VARIABLE_READ_ONLY |
 | |
| 				   EFI_VARIABLE_NON_VOLATILE |
 | |
| 				   EFI_VARIABLE_BOOTSERVICE_ACCESS |
 | |
| 				   EFI_VARIABLE_RUNTIME_ACCESS,
 | |
| 				   22, variable_name16, false);
 | |
| 	if (ret != EFI_SUCCESS)
 | |
| 		log_err("Setting %ls failed\n", u"CapsuleLast");
 | |
| }
 | |
| 
 | |
| #ifdef CONFIG_EFI_CAPSULE_FIRMWARE_MANAGEMENT
 | |
| /**
 | |
|  * efi_fmp_find - search for Firmware Management Protocol drivers
 | |
|  * @image_type:		Image type guid
 | |
|  * @image_index:	Image Index
 | |
|  * @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, u8 image_index, 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++) {
 | |
| 		struct efi_handler *fmp_handler;
 | |
| 
 | |
| 		ret = efi_search_protocol(
 | |
| 				*handle, &efi_guid_firmware_management_protocol,
 | |
| 				&fmp_handler);
 | |
| 		if (ret != EFI_SUCCESS)
 | |
| 			continue;
 | |
| 		fmp = fmp_handler->protocol_interface;
 | |
| 
 | |
| 		/* 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) &&
 | |
| 			    (desc->image_index == image_index) &&
 | |
| 			    (!instance ||
 | |
| 			     !desc->hardware_instance ||
 | |
| 			      desc->hardware_instance == instance))
 | |
| 				found = true;
 | |
| 		}
 | |
| 
 | |
| skip:
 | |
| 		efi_free_pool(package_version_name);
 | |
| 		free(image_info);
 | |
| 		if (found)
 | |
| 			return fmp;
 | |
| 	}
 | |
| 
 | |
| 	return NULL;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * efi_remove_auth_hdr - remove authentication data from image
 | |
|  * @image:	Pointer to pointer to Image
 | |
|  * @image_size:	Pointer to Image size
 | |
|  *
 | |
|  * Remove the authentication data from image if possible.
 | |
|  * Update @image and @image_size.
 | |
|  *
 | |
|  * Return:		status code
 | |
|  */
 | |
| static efi_status_t efi_remove_auth_hdr(void **image, efi_uintn_t *image_size)
 | |
| {
 | |
| 	struct efi_firmware_image_authentication *auth_hdr;
 | |
| 	efi_status_t ret = EFI_INVALID_PARAMETER;
 | |
| 
 | |
| 	auth_hdr = (struct efi_firmware_image_authentication *)*image;
 | |
| 	if (*image_size < sizeof(*auth_hdr))
 | |
| 		goto out;
 | |
| 
 | |
| 	if (auth_hdr->auth_info.hdr.dwLength <=
 | |
| 	    offsetof(struct win_certificate_uefi_guid, cert_data))
 | |
| 		goto out;
 | |
| 
 | |
| 	*image = (uint8_t *)*image + sizeof(auth_hdr->monotonic_count) +
 | |
| 		auth_hdr->auth_info.hdr.dwLength;
 | |
| 	*image_size = *image_size - auth_hdr->auth_info.hdr.dwLength -
 | |
| 		sizeof(auth_hdr->monotonic_count);
 | |
| 
 | |
| 	ret = EFI_SUCCESS;
 | |
| out:
 | |
| 	return ret;
 | |
| }
 | |
| 
 | |
| #if defined(CONFIG_EFI_CAPSULE_AUTHENTICATE)
 | |
| int efi_get_public_key_data(void **pkey, efi_uintn_t *pkey_len)
 | |
| {
 | |
| 	const void *fdt_blob = gd->fdt_blob;
 | |
| 	const void *blob;
 | |
| 	const char *cnode_name = "capsule-key";
 | |
| 	const char *snode_name = "signature";
 | |
| 	int sig_node;
 | |
| 	int len;
 | |
| 
 | |
| 	sig_node = fdt_subnode_offset(fdt_blob, 0, snode_name);
 | |
| 	if (sig_node < 0) {
 | |
| 		log_err("Unable to get signature node offset\n");
 | |
| 
 | |
| 		return -FDT_ERR_NOTFOUND;
 | |
| 	}
 | |
| 
 | |
| 	blob = fdt_getprop(fdt_blob, sig_node, cnode_name, &len);
 | |
| 
 | |
| 	if (!blob || len < 0) {
 | |
| 		log_err("Unable to get capsule-key value\n");
 | |
| 		*pkey = NULL;
 | |
| 		*pkey_len = 0;
 | |
| 
 | |
| 		return -FDT_ERR_NOTFOUND;
 | |
| 	}
 | |
| 
 | |
| 	*pkey = (void *)blob;
 | |
| 	*pkey_len = len;
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| efi_status_t efi_capsule_authenticate(const void *capsule, efi_uintn_t capsule_size,
 | |
| 				      void **image, efi_uintn_t *image_size)
 | |
| {
 | |
| 	u8 *buf;
 | |
| 	int ret;
 | |
| 	void *fdt_pkey, *pkey;
 | |
| 	efi_uintn_t pkey_len;
 | |
| 	uint64_t monotonic_count;
 | |
| 	struct efi_signature_store *truststore;
 | |
| 	struct pkcs7_message *capsule_sig;
 | |
| 	struct efi_image_regions *regs;
 | |
| 	struct efi_firmware_image_authentication *auth_hdr;
 | |
| 	efi_status_t status;
 | |
| 
 | |
| 	status = EFI_SECURITY_VIOLATION;
 | |
| 	capsule_sig = NULL;
 | |
| 	truststore = NULL;
 | |
| 	regs = NULL;
 | |
| 
 | |
| 	/* Sanity checks */
 | |
| 	if (capsule == NULL || capsule_size == 0)
 | |
| 		goto out;
 | |
| 
 | |
| 	*image = (uint8_t *)capsule;
 | |
| 	*image_size = capsule_size;
 | |
| 	if (efi_remove_auth_hdr(image, image_size) != EFI_SUCCESS)
 | |
| 		goto out;
 | |
| 
 | |
| 	auth_hdr = (struct efi_firmware_image_authentication *)capsule;
 | |
| 	if (guidcmp(&auth_hdr->auth_info.cert_type, &efi_guid_cert_type_pkcs7))
 | |
| 		goto out;
 | |
| 
 | |
| 	memcpy(&monotonic_count, &auth_hdr->monotonic_count,
 | |
| 	       sizeof(monotonic_count));
 | |
| 
 | |
| 	/* data to be digested */
 | |
| 	regs = calloc(sizeof(*regs) + sizeof(struct image_region) * 2, 1);
 | |
| 	if (!regs)
 | |
| 		goto out;
 | |
| 
 | |
| 	regs->max = 2;
 | |
| 	efi_image_region_add(regs, (uint8_t *)*image,
 | |
| 			     (uint8_t *)*image + *image_size, 1);
 | |
| 
 | |
| 	efi_image_region_add(regs, (uint8_t *)&monotonic_count,
 | |
| 			     (uint8_t *)&monotonic_count + sizeof(monotonic_count),
 | |
| 			     1);
 | |
| 
 | |
| 	capsule_sig = efi_parse_pkcs7_header(auth_hdr->auth_info.cert_data,
 | |
| 					     auth_hdr->auth_info.hdr.dwLength
 | |
| 					     - sizeof(auth_hdr->auth_info),
 | |
| 					     &buf);
 | |
| 	if (!capsule_sig) {
 | |
| 		debug("Parsing variable's pkcs7 header failed\n");
 | |
| 		goto out;
 | |
| 	}
 | |
| 
 | |
| 	ret = efi_get_public_key_data(&fdt_pkey, &pkey_len);
 | |
| 	if (ret < 0)
 | |
| 		goto out;
 | |
| 
 | |
| 	pkey = malloc(pkey_len);
 | |
| 	if (!pkey)
 | |
| 		goto out;
 | |
| 
 | |
| 	memcpy(pkey, fdt_pkey, pkey_len);
 | |
| 	truststore = efi_build_signature_store(pkey, pkey_len);
 | |
| 	if (!truststore)
 | |
| 		goto out;
 | |
| 
 | |
| 	/* verify signature */
 | |
| 	if (efi_signature_verify(regs, capsule_sig, truststore, NULL)) {
 | |
| 		debug("Verified\n");
 | |
| 	} else {
 | |
| 		debug("Verifying variable's signature failed\n");
 | |
| 		goto out;
 | |
| 	}
 | |
| 
 | |
| 	status = EFI_SUCCESS;
 | |
| 
 | |
| out:
 | |
| 	efi_sigstore_free(truststore);
 | |
| 	pkcs7_free_message(capsule_sig);
 | |
| 	free(regs);
 | |
| 
 | |
| 	return status;
 | |
| }
 | |
| #endif /* CONFIG_EFI_CAPSULE_AUTHENTICATE */
 | |
| 
 | |
| static __maybe_unused bool fwu_empty_capsule(struct efi_capsule_header *capsule)
 | |
| {
 | |
| 	return !guidcmp(&capsule->capsule_guid,
 | |
| 			&fwu_guid_os_request_fw_revert) ||
 | |
| 		!guidcmp(&capsule->capsule_guid,
 | |
| 			 &fwu_guid_os_request_fw_accept);
 | |
| }
 | |
| 
 | |
| static __maybe_unused efi_status_t fwu_to_efi_error(int err)
 | |
| {
 | |
| 	efi_status_t ret;
 | |
| 
 | |
| 	switch(err) {
 | |
| 	case 0:
 | |
| 		ret = EFI_SUCCESS;
 | |
| 		break;
 | |
| 	case -ERANGE:
 | |
| 	case -EIO:
 | |
| 		ret = EFI_DEVICE_ERROR;
 | |
| 		break;
 | |
| 	case -EINVAL:
 | |
| 		ret = EFI_INVALID_PARAMETER;
 | |
| 		break;
 | |
| 	case -ENODEV:
 | |
| 		ret = EFI_NOT_FOUND;
 | |
| 		break;
 | |
| 	default:
 | |
| 		ret = EFI_OUT_OF_RESOURCES;
 | |
| 	}
 | |
| 
 | |
| 	return ret;
 | |
| }
 | |
| 
 | |
| static __maybe_unused efi_status_t fwu_empty_capsule_process(
 | |
| 	struct efi_capsule_header *capsule)
 | |
| {
 | |
| 	int status;
 | |
| 	u32 active_idx;
 | |
| 	efi_guid_t *image_guid;
 | |
| 	efi_status_t ret = EFI_INVALID_PARAMETER;
 | |
| 
 | |
| 	if (!guidcmp(&capsule->capsule_guid,
 | |
| 		     &fwu_guid_os_request_fw_revert)) {
 | |
| 		/*
 | |
| 		 * One of the previously updated image has
 | |
| 		 * failed the OS acceptance test. OS has
 | |
| 		 * requested to revert back to the earlier
 | |
| 		 * boot index
 | |
| 		 */
 | |
| 		status = fwu_revert_boot_index();
 | |
| 		ret = fwu_to_efi_error(status);
 | |
| 		if (ret == EFI_SUCCESS)
 | |
| 			log_debug("Reverted the FWU active_index. Recommend rebooting the system\n");
 | |
| 		else
 | |
| 			log_err("Failed to revert the FWU boot index\n");
 | |
| 	} else if (!guidcmp(&capsule->capsule_guid,
 | |
| 			    &fwu_guid_os_request_fw_accept)) {
 | |
| 		/*
 | |
| 		 * Image accepted by the OS. Set the acceptance
 | |
| 		 * status for the image.
 | |
| 		 */
 | |
| 		image_guid = (void *)(char *)capsule +
 | |
| 			capsule->header_size;
 | |
| 
 | |
| 		status = fwu_get_active_index(&active_idx);
 | |
| 		ret = fwu_to_efi_error(status);
 | |
| 		if (ret != EFI_SUCCESS) {
 | |
| 			log_err("Unable to get the active_index from the FWU metadata\n");
 | |
| 			return ret;
 | |
| 		}
 | |
| 
 | |
| 		status = fwu_accept_image(image_guid, active_idx);
 | |
| 		ret = fwu_to_efi_error(status);
 | |
| 		if (ret != EFI_SUCCESS)
 | |
| 			log_err("Unable to set the Accept bit for the image %pUs\n",
 | |
| 				image_guid);
 | |
| 
 | |
| 		status = fwu_state_machine_updates(0, active_idx);
 | |
| 		if (status < 0)
 | |
| 			ret = EFI_DEVICE_ERROR;
 | |
| 
 | |
| 	}
 | |
| 
 | |
| 	return ret;
 | |
| }
 | |
| 
 | |
| static __maybe_unused void fwu_post_update_checks(
 | |
| 	struct efi_capsule_header *capsule,
 | |
| 	bool *fw_accept_os, bool *capsule_update)
 | |
| {
 | |
| 	if (fwu_empty_capsule(capsule))
 | |
| 		*capsule_update = false;
 | |
| 	else
 | |
| 		if (!*fw_accept_os)
 | |
| 			*fw_accept_os =
 | |
| 				capsule->flags & FW_ACCEPT_OS ? true : false;
 | |
| }
 | |
| 
 | |
| static __maybe_unused efi_status_t fwu_post_update_process(bool fw_accept_os)
 | |
| {
 | |
| 	int status;
 | |
| 	uint update_index;
 | |
| 	efi_status_t ret;
 | |
| 
 | |
| 	status = fwu_plat_get_update_index(&update_index);
 | |
| 	if (status < 0) {
 | |
| 		log_err("Failed to get the FWU update_index value\n");
 | |
| 		return EFI_DEVICE_ERROR;
 | |
| 	}
 | |
| 
 | |
| 	/*
 | |
| 	 * All the capsules have been updated successfully,
 | |
| 	 * update the FWU metadata.
 | |
| 	 */
 | |
| 	log_debug("Update Complete. Now updating active_index to %u\n",
 | |
| 		  update_index);
 | |
| 	status = fwu_set_active_index(update_index);
 | |
| 	ret = fwu_to_efi_error(status);
 | |
| 	if (ret != EFI_SUCCESS) {
 | |
| 		log_err("Failed to update FWU metadata index values\n");
 | |
| 	} else {
 | |
| 		log_debug("Successfully updated the active_index\n");
 | |
| 		status = fwu_state_machine_updates(fw_accept_os ? 1 : 0,
 | |
| 						   update_index);
 | |
| 		if (status < 0)
 | |
| 			ret = EFI_DEVICE_ERROR;
 | |
| 	}
 | |
| 
 | |
| 	return ret;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * 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, image_binary_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_guid_t *image_type_id;
 | |
| 	efi_status_t ret = EFI_SUCCESS;
 | |
| 	int status;
 | |
| 	uint update_index;
 | |
| 	bool fw_accept_os;
 | |
| 
 | |
| 	if (IS_ENABLED(CONFIG_FWU_MULTI_BANK_UPDATE)) {
 | |
| 		if (fwu_empty_capsule_checks_pass() &&
 | |
| 		    fwu_empty_capsule(capsule_data))
 | |
| 			return fwu_empty_capsule_process(capsule_data);
 | |
| 
 | |
| 		if (!fwu_update_checks_pass()) {
 | |
| 			log_err("FWU checks failed. Cannot start update\n");
 | |
| 			return EFI_INVALID_PARAMETER;
 | |
| 		}
 | |
| 
 | |
| 
 | |
| 		/* Obtain the update_index from the platform */
 | |
| 		status = fwu_plat_get_update_index(&update_index);
 | |
| 		if (status < 0) {
 | |
| 			log_err("Failed to get the FWU update_index value\n");
 | |
| 			return EFI_DEVICE_ERROR;
 | |
| 		}
 | |
| 
 | |
| 		fw_accept_os = capsule_data->flags & FW_ACCEPT_OS ? 0x1 : 0x0;
 | |
| 	}
 | |
| 
 | |
| 	if (guidcmp(&capsule_data->capsule_guid,
 | |
| 		    &efi_guid_firmware_management_capsule_id)) {
 | |
| 		log_err("Unsupported capsule type: %pUs\n",
 | |
| 			&capsule_data->capsule_guid);
 | |
| 		return EFI_UNSUPPORTED;
 | |
| 	}
 | |
| 
 | |
| 	/* 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("Capsule does not have 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 */
 | |
| 		fmp = efi_fmp_find(&image->update_image_type_id,
 | |
| 				   image->update_image_index,
 | |
| 				   image->update_hardware_instance,
 | |
| 				   handles, no_handles);
 | |
| 		if (!fmp) {
 | |
| 			log_err("FMP driver not found for firmware type %pUs, hardware instance %lld\n",
 | |
| 				&image->update_image_type_id,
 | |
| 				image->update_hardware_instance);
 | |
| 			ret = EFI_UNSUPPORTED;
 | |
| 			goto out;
 | |
| 		}
 | |
| 
 | |
| 		/* do update */
 | |
| 		if (IS_ENABLED(CONFIG_EFI_CAPSULE_AUTHENTICATE) &&
 | |
| 		    !(image->image_capsule_support &
 | |
| 				CAPSULE_SUPPORT_AUTHENTICATION)) {
 | |
| 			/* no signature */
 | |
| 			ret = EFI_SECURITY_VIOLATION;
 | |
| 			goto out;
 | |
| 		}
 | |
| 
 | |
| 		image_binary = (void *)image + sizeof(*image);
 | |
| 		image_binary_size = image->update_image_size;
 | |
| 		vendor_code = image_binary + image_binary_size;
 | |
| 		if (!IS_ENABLED(CONFIG_EFI_CAPSULE_AUTHENTICATE) &&
 | |
| 		    (image->image_capsule_support &
 | |
| 				CAPSULE_SUPPORT_AUTHENTICATION)) {
 | |
| 			ret = efi_remove_auth_hdr(&image_binary,
 | |
| 						  &image_binary_size);
 | |
| 			if (ret != EFI_SUCCESS)
 | |
| 				goto out;
 | |
| 		}
 | |
| 
 | |
| 		abort_reason = NULL;
 | |
| 		ret = EFI_CALL(fmp->set_image(fmp, image->update_image_index,
 | |
| 					      image_binary,
 | |
| 					      image_binary_size,
 | |
| 					      vendor_code, NULL,
 | |
| 					      &abort_reason));
 | |
| 		if (ret != EFI_SUCCESS) {
 | |
| 			log_err("Firmware update failed: %ls\n",
 | |
| 				abort_reason);
 | |
| 			efi_free_pool(abort_reason);
 | |
| 			goto out;
 | |
| 		}
 | |
| 
 | |
| 		if (IS_ENABLED(CONFIG_FWU_MULTI_BANK_UPDATE)) {
 | |
| 			image_type_id = &image->update_image_type_id;
 | |
| 			if (!fw_accept_os) {
 | |
| 				/*
 | |
| 				 * The OS will not be accepting the firmware
 | |
| 				 * images. Set the accept bit of all the
 | |
| 				 * images contained in this capsule.
 | |
| 				 */
 | |
| 				status = fwu_accept_image(image_type_id,
 | |
| 							  update_index);
 | |
| 			} else {
 | |
| 				status = fwu_clear_accept_image(image_type_id,
 | |
| 								update_index);
 | |
| 			}
 | |
| 			ret = fwu_to_efi_error(status);
 | |
| 			if (ret != EFI_SUCCESS) {
 | |
| 				log_err("Unable to %s the accept bit for the image %pUs\n",
 | |
| 					fw_accept_os ? "clear" : "set",
 | |
| 					image_type_id);
 | |
| 				goto out;
 | |
| 			}
 | |
| 
 | |
| 			log_debug("%s the accepted bit for Image %pUs\n",
 | |
| 				  fw_accept_os ? "Cleared" : "Set",
 | |
| 				  image_type_id);
 | |
| 		}
 | |
| 
 | |
| 	}
 | |
| 
 | |
| 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
 | |
|  * @capsule_count:		Number of pointers in capsule_header_array
 | |
|  * @scatter_gather_list:	Array of physical address pointers
 | |
|  *
 | |
|  * This function implements the UpdateCapsule() runtime service.
 | |
|  *
 | |
|  * See the Unified Extensible Firmware Interface (UEFI) specification for
 | |
|  * details.
 | |
|  *
 | |
|  * Return:			status code
 | |
|  */
 | |
| efi_status_t EFIAPI efi_update_capsule(
 | |
| 		struct efi_capsule_header **capsule_header_array,
 | |
| 		efi_uintn_t capsule_count,
 | |
| 		u64 scatter_gather_list)
 | |
| {
 | |
| 	struct efi_capsule_header *capsule;
 | |
| 	unsigned int i;
 | |
| 	efi_status_t ret;
 | |
| 
 | |
| 	EFI_ENTRY("%p, %zu, %llu\n", capsule_header_array, capsule_count,
 | |
| 		  scatter_gather_list);
 | |
| 
 | |
| 	if (!capsule_count) {
 | |
| 		ret = EFI_INVALID_PARAMETER;
 | |
| 		goto out;
 | |
| 	}
 | |
| 
 | |
| 	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("Capsule does not have enough data\n");
 | |
| 			continue;
 | |
| 		}
 | |
| 
 | |
| 		log_debug("Capsule[%d] (guid:%pUs)\n",
 | |
| 			  i, &capsule->capsule_guid);
 | |
| 		ret  = efi_capsule_update_firmware(capsule);
 | |
| 		if (ret != EFI_SUCCESS)
 | |
| 			goto out;
 | |
| 	}
 | |
| 
 | |
| 	if (IS_ENABLED(CONFIG_EFI_ESRT)) {
 | |
| 		/* Rebuild the ESRT to reflect any updated FW images. */
 | |
| 		ret = efi_esrt_populate();
 | |
| 		if (ret != EFI_SUCCESS)
 | |
| 			log_warning("ESRT update failed\n");
 | |
| 	}
 | |
| out:
 | |
| 
 | |
| 	return EFI_EXIT(ret);
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * efi_query_capsule_caps() - check if capsule is supported
 | |
|  * @capsule_header_array:	Array of virtual pointers
 | |
|  * @capsule_count:		Number of pointers in capsule_header_array
 | |
|  * @maximum_capsule_size:	Maximum capsule size
 | |
|  * @reset_type:			Type of reset needed for capsule update
 | |
|  *
 | |
|  * This function implements the QueryCapsuleCapabilities() runtime service.
 | |
|  *
 | |
|  * See the Unified Extensible Firmware Interface (UEFI) specification for
 | |
|  * details.
 | |
|  *
 | |
|  * Return:			status code
 | |
|  */
 | |
| efi_status_t EFIAPI efi_query_capsule_caps(
 | |
| 		struct efi_capsule_header **capsule_header_array,
 | |
| 		efi_uintn_t capsule_count,
 | |
| 		u64 *maximum_capsule_size,
 | |
| 		u32 *reset_type)
 | |
| {
 | |
| 	struct efi_capsule_header *capsule __attribute__((unused));
 | |
| 	unsigned int i;
 | |
| 	efi_status_t ret;
 | |
| 
 | |
| 	EFI_ENTRY("%p, %zu, %p, %p\n", capsule_header_array, capsule_count,
 | |
| 		  maximum_capsule_size, reset_type);
 | |
| 
 | |
| 	if (!maximum_capsule_size) {
 | |
| 		ret = EFI_INVALID_PARAMETER;
 | |
| 		goto out;
 | |
| 	}
 | |
| 
 | |
| 	*maximum_capsule_size = U64_MAX;
 | |
| 	*reset_type = EFI_RESET_COLD;
 | |
| 
 | |
| 	ret = EFI_SUCCESS;
 | |
| 	for (i = 0, capsule = *capsule_header_array; i < capsule_count;
 | |
| 	     i++, capsule = *(++capsule_header_array)) {
 | |
| 		/* TODO */
 | |
| 	}
 | |
| out:
 | |
| 	return EFI_EXIT(ret);
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * efi_load_capsule_drivers - initialize capsule drivers
 | |
|  *
 | |
|  * Generic FMP drivers backed by DFU
 | |
|  *
 | |
|  * Return:	status code
 | |
|  */
 | |
| efi_status_t __weak efi_load_capsule_drivers(void)
 | |
| {
 | |
| 	__maybe_unused efi_handle_t handle;
 | |
| 	efi_status_t ret = EFI_SUCCESS;
 | |
| 
 | |
| 	if (IS_ENABLED(CONFIG_EFI_CAPSULE_FIRMWARE_FIT)) {
 | |
| 		handle = NULL;
 | |
| 		ret = efi_install_multiple_protocol_interfaces(&handle,
 | |
| 							       &efi_guid_firmware_management_protocol,
 | |
| 							       &efi_fmp_fit,
 | |
| 							       NULL);
 | |
| 	}
 | |
| 
 | |
| 	if (IS_ENABLED(CONFIG_EFI_CAPSULE_FIRMWARE_RAW)) {
 | |
| 		handle = NULL;
 | |
| 		ret = efi_install_multiple_protocol_interfaces(&handle,
 | |
| 							       &efi_guid_firmware_management_protocol,
 | |
| 							       &efi_fmp_raw,
 | |
| 							       NULL);
 | |
| 	}
 | |
| 
 | |
| 	return ret;
 | |
| }
 | |
| 
 | |
| #ifdef CONFIG_EFI_CAPSULE_ON_DISK
 | |
| /**
 | |
|  * get_dp_device - retrieve a device  path from boot variable
 | |
|  * @boot_var:	Boot variable name
 | |
|  * @device_dp	Device path
 | |
|  *
 | |
|  * Retrieve a device patch from boot variable, @boot_var.
 | |
|  *
 | |
|  * Return:	status code
 | |
|  */
 | |
| static efi_status_t get_dp_device(u16 *boot_var,
 | |
| 				  struct efi_device_path **device_dp)
 | |
| {
 | |
| 	void *buf = NULL;
 | |
| 	efi_uintn_t size;
 | |
| 	struct efi_load_option lo;
 | |
| 	struct efi_device_path *file_dp;
 | |
| 	efi_status_t ret;
 | |
| 
 | |
| 	size = 0;
 | |
| 	ret = efi_get_variable_int(boot_var, &efi_global_variable_guid,
 | |
| 				   NULL, &size, NULL, NULL);
 | |
| 	if (ret == EFI_BUFFER_TOO_SMALL) {
 | |
| 		buf = malloc(size);
 | |
| 		if (!buf)
 | |
| 			return EFI_OUT_OF_RESOURCES;
 | |
| 		ret = efi_get_variable_int(boot_var, &efi_global_variable_guid,
 | |
| 					   NULL, &size, buf, NULL);
 | |
| 	}
 | |
| 	if (ret != EFI_SUCCESS)
 | |
| 		return ret;
 | |
| 
 | |
| 	efi_deserialize_load_option(&lo, buf, &size);
 | |
| 
 | |
| 	if (lo.attributes & LOAD_OPTION_ACTIVE) {
 | |
| 		efi_dp_split_file_path(lo.file_path, device_dp, &file_dp);
 | |
| 		efi_free_pool(file_dp);
 | |
| 
 | |
| 		ret = EFI_SUCCESS;
 | |
| 	} else {
 | |
| 		ret = EFI_NOT_FOUND;
 | |
| 	}
 | |
| 
 | |
| 	free(buf);
 | |
| 
 | |
| 	return ret;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * device_is_present_and_system_part - check if a device exists
 | |
|  *
 | |
|  * Check if a device pointed to by the device path, @dp, exists and is
 | |
|  * located in UEFI system partition.
 | |
|  *
 | |
|  * @dp		device path
 | |
|  * Return:	true - yes, false - no
 | |
|  */
 | |
| static bool device_is_present_and_system_part(struct efi_device_path *dp)
 | |
| {
 | |
| 	efi_handle_t handle;
 | |
| 	struct efi_device_path *rem;
 | |
| 
 | |
| 	/* Check device exists */
 | |
| 	handle = efi_dp_find_obj(dp, NULL, NULL);
 | |
| 	if (!handle)
 | |
| 		return false;
 | |
| 
 | |
| 	/* Check device is on system partition */
 | |
| 	handle = efi_dp_find_obj(dp, &efi_system_partition_guid, &rem);
 | |
| 	if (!handle)
 | |
| 		return false;
 | |
| 
 | |
| 	return true;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * find_boot_device - identify the boot device
 | |
|  *
 | |
|  * Identify the boot device from boot-related variables as UEFI
 | |
|  * specification describes and put its handle into bootdev_root.
 | |
|  *
 | |
|  * Return:	status code
 | |
|  */
 | |
| static efi_status_t find_boot_device(void)
 | |
| {
 | |
| 	char boot_var[9];
 | |
| 	u16 boot_var16[9], *p, bootnext, *boot_order = NULL;
 | |
| 	efi_uintn_t size;
 | |
| 	int i, num;
 | |
| 	struct efi_simple_file_system_protocol *volume;
 | |
| 	struct efi_device_path *boot_dev = NULL;
 | |
| 	efi_status_t ret;
 | |
| 
 | |
| 	/* find active boot device in BootNext */
 | |
| 	bootnext = 0;
 | |
| 	size = sizeof(bootnext);
 | |
| 	ret = efi_get_variable_int(u"BootNext",
 | |
| 				   (efi_guid_t *)&efi_global_variable_guid,
 | |
| 				   NULL, &size, &bootnext, NULL);
 | |
| 	if (ret == EFI_SUCCESS || ret == EFI_BUFFER_TOO_SMALL) {
 | |
| 		/* BootNext does exist here */
 | |
| 		if (ret == EFI_BUFFER_TOO_SMALL || size != sizeof(u16)) {
 | |
| 			log_err("BootNext must be 16-bit integer\n");
 | |
| 			goto skip;
 | |
| 		}
 | |
| 		sprintf((char *)boot_var, "Boot%04X", bootnext);
 | |
| 		p = boot_var16;
 | |
| 		utf8_utf16_strcpy(&p, boot_var);
 | |
| 
 | |
| 		ret = get_dp_device(boot_var16, &boot_dev);
 | |
| 		if (ret == EFI_SUCCESS) {
 | |
| 			if (device_is_present_and_system_part(boot_dev)) {
 | |
| 				goto found;
 | |
| 			} else {
 | |
| 				efi_free_pool(boot_dev);
 | |
| 				boot_dev = NULL;
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| skip:
 | |
| 	/* find active boot device in BootOrder */
 | |
| 	size = 0;
 | |
| 	ret = efi_get_variable_int(u"BootOrder", &efi_global_variable_guid,
 | |
| 				   NULL, &size, NULL, NULL);
 | |
| 	if (ret == EFI_BUFFER_TOO_SMALL) {
 | |
| 		boot_order = malloc(size);
 | |
| 		if (!boot_order) {
 | |
| 			ret = EFI_OUT_OF_RESOURCES;
 | |
| 			goto out;
 | |
| 		}
 | |
| 
 | |
| 		ret = efi_get_variable_int(u"BootOrder",
 | |
| 					   &efi_global_variable_guid,
 | |
| 					   NULL, &size, boot_order, NULL);
 | |
| 	}
 | |
| 	if (ret != EFI_SUCCESS)
 | |
| 		goto out;
 | |
| 
 | |
| 	/* check in higher order */
 | |
| 	num = size / sizeof(u16);
 | |
| 	for (i = 0; i < num; i++) {
 | |
| 		sprintf((char *)boot_var, "Boot%04X", boot_order[i]);
 | |
| 		p = boot_var16;
 | |
| 		utf8_utf16_strcpy(&p, boot_var);
 | |
| 		ret = get_dp_device(boot_var16, &boot_dev);
 | |
| 		if (ret != EFI_SUCCESS)
 | |
| 			continue;
 | |
| 
 | |
| 		if (device_is_present_and_system_part(boot_dev))
 | |
| 			break;
 | |
| 
 | |
| 		efi_free_pool(boot_dev);
 | |
| 		boot_dev = NULL;
 | |
| 	}
 | |
| found:
 | |
| 	if (boot_dev) {
 | |
| 		log_debug("Boot device %pD\n", boot_dev);
 | |
| 
 | |
| 		volume = efi_fs_from_path(boot_dev);
 | |
| 		if (!volume)
 | |
| 			ret = EFI_DEVICE_ERROR;
 | |
| 		else
 | |
| 			ret = EFI_CALL(volume->open_volume(volume,
 | |
| 							   &bootdev_root));
 | |
| 		efi_free_pool(boot_dev);
 | |
| 	} else {
 | |
| 		ret = EFI_NOT_FOUND;
 | |
| 	}
 | |
| out:
 | |
| 	free(boot_order);
 | |
| 
 | |
| 	return ret;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * efi_capsule_scan_dir - traverse a capsule directory in boot device
 | |
|  * @files:	Array of file names
 | |
|  * @num:	Number of elements in @files
 | |
|  *
 | |
|  * Traverse a capsule directory in boot device.
 | |
|  * Called by initialization code, and returns an array of capsule file
 | |
|  * names in @files.
 | |
|  *
 | |
|  * Return:	status code
 | |
|  */
 | |
| static efi_status_t efi_capsule_scan_dir(u16 ***files, unsigned int *num)
 | |
| {
 | |
| 	struct efi_file_handle *dirh;
 | |
| 	struct efi_file_info *dirent;
 | |
| 	efi_uintn_t dirent_size, tmp_size;
 | |
| 	unsigned int count;
 | |
| 	u16 **tmp_files;
 | |
| 	efi_status_t ret;
 | |
| 
 | |
| 	ret = find_boot_device();
 | |
| 	if (ret == EFI_NOT_FOUND) {
 | |
| 		log_debug("Boot device is not set\n");
 | |
| 		*num = 0;
 | |
| 		return EFI_SUCCESS;
 | |
| 	} else if (ret != EFI_SUCCESS) {
 | |
| 		return EFI_DEVICE_ERROR;
 | |
| 	}
 | |
| 
 | |
| 	/* count capsule files */
 | |
| 	ret = EFI_CALL((*bootdev_root->open)(bootdev_root, &dirh,
 | |
| 					     EFI_CAPSULE_DIR,
 | |
| 					     EFI_FILE_MODE_READ, 0));
 | |
| 	if (ret != EFI_SUCCESS) {
 | |
| 		*num = 0;
 | |
| 		return EFI_SUCCESS;
 | |
| 	}
 | |
| 
 | |
| 	dirent_size = 256;
 | |
| 	dirent = malloc(dirent_size);
 | |
| 	if (!dirent)
 | |
| 		return EFI_OUT_OF_RESOURCES;
 | |
| 
 | |
| 	count = 0;
 | |
| 	while (1) {
 | |
| 		tmp_size = dirent_size;
 | |
| 		ret = EFI_CALL((*dirh->read)(dirh, &tmp_size, dirent));
 | |
| 		if (ret == EFI_BUFFER_TOO_SMALL) {
 | |
| 			struct efi_file_info *old_dirent = dirent;
 | |
| 
 | |
| 			dirent = realloc(dirent, tmp_size);
 | |
| 			if (!dirent) {
 | |
| 				dirent = old_dirent;
 | |
| 				ret = EFI_OUT_OF_RESOURCES;
 | |
| 				goto err;
 | |
| 			}
 | |
| 			dirent_size = tmp_size;
 | |
| 			ret = EFI_CALL((*dirh->read)(dirh, &tmp_size, dirent));
 | |
| 		}
 | |
| 		if (ret != EFI_SUCCESS)
 | |
| 			goto err;
 | |
| 		if (!tmp_size)
 | |
| 			break;
 | |
| 
 | |
| 		if (!(dirent->attribute & EFI_FILE_DIRECTORY))
 | |
| 			count++;
 | |
| 	}
 | |
| 
 | |
| 	ret = EFI_CALL((*dirh->setpos)(dirh, 0));
 | |
| 	if (ret != EFI_SUCCESS)
 | |
| 		goto err;
 | |
| 
 | |
| 	/* make a list */
 | |
| 	tmp_files = malloc(count * sizeof(*tmp_files));
 | |
| 	if (!tmp_files) {
 | |
| 		ret = EFI_OUT_OF_RESOURCES;
 | |
| 		goto err;
 | |
| 	}
 | |
| 
 | |
| 	count = 0;
 | |
| 	while (1) {
 | |
| 		tmp_size = dirent_size;
 | |
| 		ret = EFI_CALL((*dirh->read)(dirh, &tmp_size, dirent));
 | |
| 		if (ret != EFI_SUCCESS)
 | |
| 			goto err;
 | |
| 		if (!tmp_size)
 | |
| 			break;
 | |
| 
 | |
| 		if (!(dirent->attribute & EFI_FILE_DIRECTORY) &&
 | |
| 		    u16_strcmp(dirent->file_name, u".") &&
 | |
| 		    u16_strcmp(dirent->file_name, u".."))
 | |
| 			tmp_files[count++] = u16_strdup(dirent->file_name);
 | |
| 	}
 | |
| 	/* ignore an error */
 | |
| 	EFI_CALL((*dirh->close)(dirh));
 | |
| 
 | |
| 	/*
 | |
| 	 * Capsule files are applied in case insensitive alphabetic order
 | |
| 	 *
 | |
| 	 * TODO: special handling of rightmost period
 | |
| 	 */
 | |
| 	qsort(tmp_files, count, sizeof(*tmp_files),
 | |
| 	      (int (*)(const void *, const void *))u16_strcasecmp);
 | |
| 	*files = tmp_files;
 | |
| 	*num = count;
 | |
| 	ret = EFI_SUCCESS;
 | |
| err:
 | |
| 	free(dirent);
 | |
| 
 | |
| 	return ret;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * efi_capsule_read_file - read in a capsule file
 | |
|  * @filename:	File name
 | |
|  * @capsule:	Pointer to buffer for capsule
 | |
|  *
 | |
|  * Read a capsule file and put its content in @capsule.
 | |
|  *
 | |
|  * Return:	status code
 | |
|  */
 | |
| static efi_status_t efi_capsule_read_file(const u16 *filename,
 | |
| 					  struct efi_capsule_header **capsule)
 | |
| {
 | |
| 	struct efi_file_handle *dirh, *fh;
 | |
| 	struct efi_file_info *file_info = NULL;
 | |
| 	struct efi_capsule_header *buf = NULL;
 | |
| 	efi_uintn_t size;
 | |
| 	efi_status_t ret;
 | |
| 
 | |
| 	ret = EFI_CALL((*bootdev_root->open)(bootdev_root, &dirh,
 | |
| 					     EFI_CAPSULE_DIR,
 | |
| 					     EFI_FILE_MODE_READ, 0));
 | |
| 	if (ret != EFI_SUCCESS)
 | |
| 		return ret;
 | |
| 	ret = EFI_CALL((*dirh->open)(dirh, &fh, (u16 *)filename,
 | |
| 				     EFI_FILE_MODE_READ, 0));
 | |
| 	/* ignore an error */
 | |
| 	EFI_CALL((*dirh->close)(dirh));
 | |
| 	if (ret != EFI_SUCCESS)
 | |
| 		return ret;
 | |
| 
 | |
| 	/* file size */
 | |
| 	size = 0;
 | |
| 	ret = EFI_CALL((*fh->getinfo)(fh, &efi_file_info_guid,
 | |
| 				      &size, file_info));
 | |
| 	if (ret == EFI_BUFFER_TOO_SMALL) {
 | |
| 		file_info = malloc(size);
 | |
| 		if (!file_info) {
 | |
| 			ret = EFI_OUT_OF_RESOURCES;
 | |
| 			goto err;
 | |
| 		}
 | |
| 		ret = EFI_CALL((*fh->getinfo)(fh, &efi_file_info_guid,
 | |
| 					      &size, file_info));
 | |
| 	}
 | |
| 	if (ret != EFI_SUCCESS)
 | |
| 		goto err;
 | |
| 	size = file_info->file_size;
 | |
| 	free(file_info);
 | |
| 	buf = malloc(size);
 | |
| 	if (!buf) {
 | |
| 		ret = EFI_OUT_OF_RESOURCES;
 | |
| 		goto err;
 | |
| 	}
 | |
| 
 | |
| 	/* fetch data */
 | |
| 	ret = EFI_CALL((*fh->read)(fh, &size, buf));
 | |
| 	if (ret == EFI_SUCCESS) {
 | |
| 		if (size >= buf->capsule_image_size) {
 | |
| 			*capsule = buf;
 | |
| 		} else {
 | |
| 			free(buf);
 | |
| 			ret = EFI_INVALID_PARAMETER;
 | |
| 		}
 | |
| 	} else {
 | |
| 		free(buf);
 | |
| 	}
 | |
| err:
 | |
| 	EFI_CALL((*fh->close)(fh));
 | |
| 
 | |
| 	return ret;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * efi_capsule_delete_file - delete a capsule file
 | |
|  * @filename:	File name
 | |
|  *
 | |
|  * Delete a capsule file from capsule directory.
 | |
|  *
 | |
|  * Return:	status code
 | |
|  */
 | |
| static efi_status_t efi_capsule_delete_file(const u16 *filename)
 | |
| {
 | |
| 	struct efi_file_handle *dirh, *fh;
 | |
| 	efi_status_t ret;
 | |
| 
 | |
| 	ret = EFI_CALL((*bootdev_root->open)(bootdev_root, &dirh,
 | |
| 					     EFI_CAPSULE_DIR,
 | |
| 					     EFI_FILE_MODE_READ, 0));
 | |
| 	if (ret != EFI_SUCCESS)
 | |
| 		return ret;
 | |
| 	ret = EFI_CALL((*dirh->open)(dirh, &fh, (u16 *)filename,
 | |
| 				     EFI_FILE_MODE_READ, 0));
 | |
| 	/* ignore an error */
 | |
| 	EFI_CALL((*dirh->close)(dirh));
 | |
| 
 | |
| 	if (ret == EFI_SUCCESS)
 | |
| 		ret = EFI_CALL((*fh->delete)(fh));
 | |
| 
 | |
| 	return ret;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * efi_capsule_scan_done - reset a scan help function
 | |
|  *
 | |
|  * Reset a scan help function
 | |
|  */
 | |
| static void efi_capsule_scan_done(void)
 | |
| {
 | |
| 	EFI_CALL((*bootdev_root->close)(bootdev_root));
 | |
| 	bootdev_root = NULL;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * check_run_capsules() - check whether capsule update should run
 | |
|  *
 | |
|  * The spec says OsIndications must be set in order to run the capsule update
 | |
|  * on-disk.  Since U-Boot doesn't support runtime SetVariable, allow capsules to
 | |
|  * run explicitly if CONFIG_EFI_IGNORE_OSINDICATIONS is selected
 | |
|  *
 | |
|  * Return:	EFI_SUCCESS if update to run, EFI_NOT_FOUND otherwise
 | |
|  */
 | |
| static efi_status_t check_run_capsules(void)
 | |
| {
 | |
| 	u64 os_indications = 0x0;
 | |
| 	efi_uintn_t size;
 | |
| 	efi_status_t r;
 | |
| 
 | |
| 	size = sizeof(os_indications);
 | |
| 	r = efi_get_variable_int(u"OsIndications", &efi_global_variable_guid,
 | |
| 				 NULL, &size, &os_indications, NULL);
 | |
| 	if (!IS_ENABLED(CONFIG_EFI_IGNORE_OSINDICATIONS) &&
 | |
| 	    (r != EFI_SUCCESS || size != sizeof(os_indications)))
 | |
| 		return EFI_NOT_FOUND;
 | |
| 
 | |
| 	if (os_indications &
 | |
| 	    EFI_OS_INDICATIONS_FILE_CAPSULE_DELIVERY_SUPPORTED) {
 | |
| 		os_indications &=
 | |
| 			~EFI_OS_INDICATIONS_FILE_CAPSULE_DELIVERY_SUPPORTED;
 | |
| 		r = efi_set_variable_int(u"OsIndications",
 | |
| 					 &efi_global_variable_guid,
 | |
| 					 EFI_VARIABLE_NON_VOLATILE |
 | |
| 					 EFI_VARIABLE_BOOTSERVICE_ACCESS |
 | |
| 					 EFI_VARIABLE_RUNTIME_ACCESS,
 | |
| 					 sizeof(os_indications),
 | |
| 					 &os_indications, false);
 | |
| 		if (r != EFI_SUCCESS)
 | |
| 			log_err("Setting %ls failed\n", L"OsIndications");
 | |
| 		return EFI_SUCCESS;
 | |
| 	} else if (IS_ENABLED(CONFIG_EFI_IGNORE_OSINDICATIONS)) {
 | |
| 		return EFI_SUCCESS;
 | |
| 	} else {
 | |
| 		return EFI_NOT_FOUND;
 | |
| 	}
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * efi_launch_capsule - launch capsules
 | |
|  *
 | |
|  * Launch all the capsules in system at boot time.
 | |
|  * Called by efi init code
 | |
|  *
 | |
|  * Return:	status codde
 | |
|  */
 | |
| efi_status_t efi_launch_capsules(void)
 | |
| {
 | |
| 	struct efi_capsule_header *capsule = NULL;
 | |
| 	u16 **files;
 | |
| 	unsigned int nfiles, index, index_max, i;
 | |
| 	efi_status_t ret;
 | |
| 	bool capsule_update = true;
 | |
| 	bool update_status = true;
 | |
| 	bool fw_accept_os = false;
 | |
| 
 | |
| 	if (check_run_capsules() != EFI_SUCCESS)
 | |
| 		return EFI_SUCCESS;
 | |
| 
 | |
| 	index_max = get_max_capsule();
 | |
| 	index = get_last_capsule();
 | |
| 
 | |
| 	/*
 | |
| 	 * Find capsules on disk.
 | |
| 	 * All the capsules are collected at the beginning because
 | |
| 	 * capsule files will be removed instantly.
 | |
| 	 */
 | |
| 	nfiles = 0;
 | |
| 	files = NULL;
 | |
| 	ret = efi_capsule_scan_dir(&files, &nfiles);
 | |
| 	if (ret != EFI_SUCCESS)
 | |
| 		return ret;
 | |
| 	if (!nfiles)
 | |
| 		return EFI_SUCCESS;
 | |
| 
 | |
| 	/* Launch capsules */
 | |
| 	for (i = 0, ++index; i < nfiles; i++, index++) {
 | |
| 		log_debug("Applying %ls\n", files[i]);
 | |
| 		if (index > index_max)
 | |
| 			index = 0;
 | |
| 		ret = efi_capsule_read_file(files[i], &capsule);
 | |
| 		if (ret == EFI_SUCCESS) {
 | |
| 			ret = efi_capsule_update_firmware(capsule);
 | |
| 			if (ret != EFI_SUCCESS) {
 | |
| 				log_err("Applying capsule %ls failed.\n",
 | |
| 					files[i]);
 | |
| 				update_status = false;
 | |
| 			} else {
 | |
| 				log_info("Applying capsule %ls succeeded.\n",
 | |
| 					 files[i]);
 | |
| 				if (IS_ENABLED(CONFIG_FWU_MULTI_BANK_UPDATE)) {
 | |
| 					fwu_post_update_checks(capsule,
 | |
| 							       &fw_accept_os,
 | |
| 							       &capsule_update);
 | |
| 				}
 | |
| 			}
 | |
| 
 | |
| 			/* create CapsuleXXXX */
 | |
| 			set_capsule_result(index, capsule, ret);
 | |
| 
 | |
| 			free(capsule);
 | |
| 		} else {
 | |
| 			log_err("Reading capsule %ls failed\n", files[i]);
 | |
| 			update_status = false;
 | |
| 		}
 | |
| 		/* delete a capsule either in case of success or failure */
 | |
| 		ret = efi_capsule_delete_file(files[i]);
 | |
| 		if (ret != EFI_SUCCESS)
 | |
| 			log_err("Deleting capsule %ls failed\n",
 | |
| 				files[i]);
 | |
| 	}
 | |
| 
 | |
| 	efi_capsule_scan_done();
 | |
| 
 | |
| 	if (IS_ENABLED(CONFIG_FWU_MULTI_BANK_UPDATE)) {
 | |
| 		if (capsule_update == true && update_status == true) {
 | |
| 			ret = fwu_post_update_process(fw_accept_os);
 | |
| 		} else if (capsule_update == true && update_status == false) {
 | |
| 			log_err("All capsules were not updated. Not updating FWU metadata\n");
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	for (i = 0; i < nfiles; i++)
 | |
| 		free(files[i]);
 | |
| 	free(files);
 | |
| 
 | |
| 	/*
 | |
| 	 * UEFI spec requires to reset system after complete processing capsule
 | |
| 	 * update on the storage.
 | |
| 	 */
 | |
| 	log_info("Reboot after firmware update.\n");
 | |
| 	/* Cold reset is required for loading the new firmware. */
 | |
| 	sysreset_walk_halt(SYSRESET_COLD);
 | |
| 	hang();
 | |
| 	/* not reach here */
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| #endif /* CONFIG_EFI_CAPSULE_ON_DISK */
 |