mirror of
				https://github.com/smaeul/u-boot.git
				synced 2025-10-26 01:28:14 +00:00 
			
		
		
		
	As we now also store device-tree device-paths in load options rename struct efi_initrd_dp to efi_lo_dp_prefix. Signed-off-by: Heinrich Schuchardt <heinrich.schuchardt@canonical.com>
		
			
				
	
	
		
			276 lines
		
	
	
		
			6.6 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			276 lines
		
	
	
		
			6.6 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| // SPDX-License-Identifier: GPL-2.0+
 | |
| /*
 | |
|  * Copyright (c) 2020, Linaro Limited
 | |
|  */
 | |
| 
 | |
| #define LOG_CATEGORY LOGC_EFI
 | |
| #include <efi_loader.h>
 | |
| #include <efi_load_initrd.h>
 | |
| #include <efi_variable.h>
 | |
| #include <fs.h>
 | |
| #include <malloc.h>
 | |
| #include <mapmem.h>
 | |
| 
 | |
| static efi_status_t EFIAPI
 | |
| efi_load_file2_initrd(struct efi_load_file_protocol *this,
 | |
| 		      struct efi_device_path *file_path, bool boot_policy,
 | |
| 		      efi_uintn_t *buffer_size, void *buffer);
 | |
| 
 | |
| static const struct efi_load_file_protocol efi_lf2_protocol = {
 | |
| 	.load_file = efi_load_file2_initrd,
 | |
| };
 | |
| 
 | |
| /*
 | |
|  * Device path defined by Linux to identify the handle providing the
 | |
|  * EFI_LOAD_FILE2_PROTOCOL used for loading the initial ramdisk.
 | |
|  */
 | |
| static const struct efi_lo_dp_prefix dp_lf2_handle = {
 | |
| 	.vendor = {
 | |
| 		{
 | |
| 		   DEVICE_PATH_TYPE_MEDIA_DEVICE,
 | |
| 		   DEVICE_PATH_SUB_TYPE_VENDOR_PATH,
 | |
| 		   sizeof(dp_lf2_handle.vendor),
 | |
| 		},
 | |
| 		EFI_INITRD_MEDIA_GUID,
 | |
| 	},
 | |
| 	.end = {
 | |
| 		DEVICE_PATH_TYPE_END,
 | |
| 		DEVICE_PATH_SUB_TYPE_END,
 | |
| 		sizeof(dp_lf2_handle.end),
 | |
| 	}
 | |
| };
 | |
| 
 | |
| static efi_handle_t efi_initrd_handle;
 | |
| 
 | |
| /**
 | |
|  * get_initrd_fp() - Get initrd device path from a FilePathList device path
 | |
|  *
 | |
|  * @initrd_fp:	the final initrd filepath
 | |
|  *
 | |
|  * Return:	status code. Caller must free initrd_fp
 | |
|  */
 | |
| static efi_status_t get_initrd_fp(struct efi_device_path **initrd_fp)
 | |
| {
 | |
| 	struct efi_device_path *dp = NULL;
 | |
| 
 | |
| 	/*
 | |
| 	 * if bootmgr is setup with and initrd, the device path will be
 | |
| 	 * in the FilePathList[] of our load options in Boot####.
 | |
| 	 * The first device path of the multi instance device path will
 | |
| 	 * start with a VenMedia and the initrds will follow.
 | |
| 	 *
 | |
| 	 * If the device path is not found return EFI_INVALID_PARAMETER.
 | |
| 	 * We can then use this specific return value and not install the
 | |
| 	 * protocol, while allowing the boot to continue
 | |
| 	 */
 | |
| 	dp = efi_get_dp_from_boot(&efi_lf2_initrd_guid);
 | |
| 	if (!dp)
 | |
| 		return EFI_INVALID_PARAMETER;
 | |
| 
 | |
| 	*initrd_fp = dp;
 | |
| 	return EFI_SUCCESS;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * efi_load_file2_initrd() - load initial RAM disk
 | |
|  *
 | |
|  * This function implements the LoadFile service of the EFI_LOAD_FILE2_PROTOCOL
 | |
|  * in order to load an initial RAM disk requested by the Linux kernel stub.
 | |
|  *
 | |
|  * See the UEFI spec for details.
 | |
|  *
 | |
|  * @this:			EFI_LOAD_FILE2_PROTOCOL instance
 | |
|  * @file_path:			media device path of the file, "" in this case
 | |
|  * @boot_policy:		must be false
 | |
|  * @buffer_size:		size of allocated buffer
 | |
|  * @buffer:			buffer to load the file
 | |
|  *
 | |
|  * Return:			status code
 | |
|  */
 | |
| static efi_status_t EFIAPI
 | |
| efi_load_file2_initrd(struct efi_load_file_protocol *this,
 | |
| 		      struct efi_device_path *file_path, bool boot_policy,
 | |
| 		      efi_uintn_t *buffer_size, void *buffer)
 | |
| {
 | |
| 	struct efi_device_path *initrd_fp = NULL;
 | |
| 	efi_status_t ret = EFI_NOT_FOUND;
 | |
| 	struct efi_file_handle *f = NULL;
 | |
| 	efi_uintn_t bs;
 | |
| 
 | |
| 	EFI_ENTRY("%p, %p, %d, %p, %p", this, file_path, boot_policy,
 | |
| 		  buffer_size, buffer);
 | |
| 
 | |
| 	if (!this || this != &efi_lf2_protocol ||
 | |
| 	    !buffer_size) {
 | |
| 		ret = EFI_INVALID_PARAMETER;
 | |
| 		goto out;
 | |
| 	}
 | |
| 
 | |
| 	if (file_path->type != dp_lf2_handle.end.type ||
 | |
| 	    file_path->sub_type != dp_lf2_handle.end.sub_type) {
 | |
| 		ret = EFI_INVALID_PARAMETER;
 | |
| 		goto out;
 | |
| 	}
 | |
| 
 | |
| 	if (boot_policy) {
 | |
| 		ret = EFI_UNSUPPORTED;
 | |
| 		goto out;
 | |
| 	}
 | |
| 
 | |
| 	ret = get_initrd_fp(&initrd_fp);
 | |
| 	if (ret != EFI_SUCCESS)
 | |
| 		goto out;
 | |
| 
 | |
| 	/* Open file */
 | |
| 	f = efi_file_from_path(initrd_fp);
 | |
| 	if (!f) {
 | |
| 		log_err("Can't find initrd specified in Boot####\n");
 | |
| 		ret = EFI_NOT_FOUND;
 | |
| 		goto out;
 | |
| 	}
 | |
| 
 | |
| 	/* Get file size */
 | |
| 	ret = efi_file_size(f, &bs);
 | |
| 	if (ret != EFI_SUCCESS)
 | |
| 		goto out;
 | |
| 
 | |
| 	if (!buffer || *buffer_size < bs) {
 | |
| 		ret = EFI_BUFFER_TOO_SMALL;
 | |
| 		*buffer_size = bs;
 | |
| 	} else {
 | |
| 		ret = EFI_CALL(f->read(f, &bs, (void *)(uintptr_t)buffer));
 | |
| 		*buffer_size = bs;
 | |
| 	}
 | |
| 
 | |
| out:
 | |
| 	efi_free_pool(initrd_fp);
 | |
| 	if (f)
 | |
| 		EFI_CALL(f->close(f));
 | |
| 	return EFI_EXIT(ret);
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * check_initrd() - Determine if the file defined as an initrd in Boot####
 | |
|  *		    load_options device path is present
 | |
|  *
 | |
|  * Return:	status code
 | |
|  */
 | |
| static efi_status_t check_initrd(void)
 | |
| {
 | |
| 	struct efi_device_path *initrd_fp = NULL;
 | |
| 	struct efi_file_handle *f;
 | |
| 	efi_status_t ret;
 | |
| 
 | |
| 	ret = get_initrd_fp(&initrd_fp);
 | |
| 	if (ret != EFI_SUCCESS)
 | |
| 		goto out;
 | |
| 
 | |
| 	/*
 | |
| 	 * If the file is not found, but the file path is set, return an error
 | |
| 	 * and trigger the bootmgr fallback
 | |
| 	 */
 | |
| 	f = efi_file_from_path(initrd_fp);
 | |
| 	if (!f) {
 | |
| 		log_err("Can't find initrd specified in Boot####\n");
 | |
| 		ret = EFI_NOT_FOUND;
 | |
| 		goto out;
 | |
| 	}
 | |
| 
 | |
| 	EFI_CALL(f->close(f));
 | |
| 
 | |
| out:
 | |
| 	efi_free_pool(initrd_fp);
 | |
| 	return ret;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * efi_initrd_deregister() - delete the handle for loading initial RAM disk
 | |
|  *
 | |
|  * This will delete the handle containing the Linux specific vendor device
 | |
|  * path and EFI_LOAD_FILE2_PROTOCOL for loading an initrd
 | |
|  *
 | |
|  * Return:	status code
 | |
|  */
 | |
| efi_status_t efi_initrd_deregister(void)
 | |
| {
 | |
| 	efi_status_t ret;
 | |
| 
 | |
| 	if (!efi_initrd_handle)
 | |
| 		return EFI_SUCCESS;
 | |
| 
 | |
| 	ret = efi_uninstall_multiple_protocol_interfaces(efi_initrd_handle,
 | |
| 							 /* initramfs */
 | |
| 							 &efi_guid_device_path,
 | |
| 							 &dp_lf2_handle,
 | |
| 							 /* LOAD_FILE2 */
 | |
| 							 &efi_guid_load_file2_protocol,
 | |
| 							 &efi_lf2_protocol,
 | |
| 							 NULL);
 | |
| 	efi_initrd_handle = NULL;
 | |
| 
 | |
| 	return ret;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * efi_initrd_return_notify() - return to efibootmgr callback
 | |
|  *
 | |
|  * @event:	the event for which this notification function is registered
 | |
|  * @context:	event context
 | |
|  */
 | |
| static void EFIAPI efi_initrd_return_notify(struct efi_event *event,
 | |
| 						  void *context)
 | |
| {
 | |
| 	efi_status_t ret;
 | |
| 
 | |
| 	EFI_ENTRY("%p, %p", event, context);
 | |
| 	ret = efi_initrd_deregister();
 | |
| 	EFI_EXIT(ret);
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * efi_initrd_register() - create handle for loading initial RAM disk
 | |
|  *
 | |
|  * This function creates a new handle and installs a Linux specific vendor
 | |
|  * device path and an EFI_LOAD_FILE2_PROTOCOL. Linux uses the device path
 | |
|  * to identify the handle and then calls the LoadFile service of the
 | |
|  * EFI_LOAD_FILE2_PROTOCOL to read the initial RAM disk.
 | |
|  *
 | |
|  * Return:	status code
 | |
|  */
 | |
| efi_status_t efi_initrd_register(void)
 | |
| {
 | |
| 	efi_status_t ret;
 | |
| 	struct efi_event *event;
 | |
| 
 | |
| 	/*
 | |
| 	 * Allow the user to continue if Boot#### file path is not set for
 | |
| 	 * an initrd
 | |
| 	 */
 | |
| 	ret = check_initrd();
 | |
| 	if (ret == EFI_INVALID_PARAMETER)
 | |
| 		return EFI_SUCCESS;
 | |
| 	if (ret != EFI_SUCCESS)
 | |
| 		return ret;
 | |
| 
 | |
| 	ret = efi_install_multiple_protocol_interfaces(&efi_initrd_handle,
 | |
| 						       /* initramfs */
 | |
| 						       &efi_guid_device_path, &dp_lf2_handle,
 | |
| 						       /* LOAD_FILE2 */
 | |
| 						       &efi_guid_load_file2_protocol,
 | |
| 						       &efi_lf2_protocol,
 | |
| 						       NULL);
 | |
| 	if (ret != EFI_SUCCESS) {
 | |
| 		log_err("installing EFI_LOAD_FILE2_PROTOCOL failed\n");
 | |
| 		return ret;
 | |
| 	}
 | |
| 
 | |
| 	ret = efi_create_event(EVT_NOTIFY_SIGNAL, TPL_CALLBACK,
 | |
| 			       efi_initrd_return_notify, NULL,
 | |
| 			       &efi_guid_event_group_return_to_efibootmgr,
 | |
| 			       &event);
 | |
| 	if (ret != EFI_SUCCESS)
 | |
| 		log_err("Creating event failed\n");
 | |
| 
 | |
| 	return ret;
 | |
| }
 |