xref: /linux/drivers/firmware/efi/libstub/loongarch-stub.c (revision adc1e5c6203cf13fe05a1ead08edcb3d3a3baae8)
1 // SPDX-License-Identifier: GPL-2.0
2 /*
3  * Author: Yun Liu <liuyun@loongson.cn>
4  *         Huacai Chen <chenhuacai@loongson.cn>
5  * Copyright (C) 2020-2022 Loongson Technology Corporation Limited
6  */
7 
8 #include <asm/efi.h>
9 #include <asm/addrspace.h>
10 #include "efistub.h"
11 #include "loongarch-stub.h"
12 
13 extern int kernel_asize;
14 extern int kernel_fsize;
15 extern int kernel_entry;
16 
17 /**
18  * efi_relocate_kernel() - copy memory area
19  * @image_addr:		pointer to address of memory area to copy
20  * @image_size:		size of memory area to copy
21  * @alloc_size:		minimum size of memory to allocate, must be greater or
22  *			equal to image_size
23  * @preferred_addr:	preferred target address
24  * @alignment:		minimum alignment of the allocated memory area. It
25  *			should be a power of two.
26  * @min_addr:		minimum target address
27  *
28  * Copy a memory area to a newly allocated memory area aligned according
29  * to @alignment but at least EFI_ALLOC_ALIGN. If the preferred address
30  * is not available, the allocated address will not be below @min_addr.
31  * On exit, @image_addr is updated to the target copy address that was used.
32  *
33  * This function is used to copy the Linux kernel verbatim. It does not apply
34  * any relocation changes.
35  *
36  * Return:		status code
37  */
38 static
efi_relocate_kernel(unsigned long * image_addr,unsigned long image_size,unsigned long alloc_size,unsigned long preferred_addr,unsigned long alignment,unsigned long min_addr)39 efi_status_t efi_relocate_kernel(unsigned long *image_addr,
40 				 unsigned long image_size,
41 				 unsigned long alloc_size,
42 				 unsigned long preferred_addr,
43 				 unsigned long alignment,
44 				 unsigned long min_addr)
45 {
46 	unsigned long cur_image_addr;
47 	unsigned long new_addr = 0;
48 	efi_status_t status;
49 	unsigned long nr_pages;
50 	efi_physical_addr_t efi_addr = preferred_addr;
51 
52 	if (!image_addr || !image_size || !alloc_size)
53 		return EFI_INVALID_PARAMETER;
54 	if (alloc_size < image_size)
55 		return EFI_INVALID_PARAMETER;
56 
57 	cur_image_addr = *image_addr;
58 
59 	/*
60 	 * The EFI firmware loader could have placed the kernel image
61 	 * anywhere in memory, but the kernel has restrictions on the
62 	 * max physical address it can run at.  Some architectures
63 	 * also have a preferred address, so first try to relocate
64 	 * to the preferred address.  If that fails, allocate as low
65 	 * as possible while respecting the required alignment.
66 	 */
67 	nr_pages = round_up(alloc_size, EFI_ALLOC_ALIGN) / EFI_PAGE_SIZE;
68 	status = efi_bs_call(allocate_pages, EFI_ALLOCATE_ADDRESS,
69 			     EFI_LOADER_DATA, nr_pages, &efi_addr);
70 	new_addr = efi_addr;
71 	/*
72 	 * If preferred address allocation failed allocate as low as
73 	 * possible.
74 	 */
75 	if (status != EFI_SUCCESS) {
76 		status = efi_low_alloc_above(alloc_size, alignment, &new_addr,
77 					     min_addr);
78 	}
79 	if (status != EFI_SUCCESS) {
80 		efi_err("Failed to allocate usable memory for kernel.\n");
81 		return status;
82 	}
83 
84 	/*
85 	 * We know source/dest won't overlap since both memory ranges
86 	 * have been allocated by UEFI, so we can safely use memcpy.
87 	 */
88 	memcpy((void *)new_addr, (void *)cur_image_addr, image_size);
89 	efi_cache_sync_image(new_addr, image_size);
90 
91 	/* Return the new address of the relocated image. */
92 	*image_addr = new_addr;
93 
94 	return status;
95 }
96 
handle_kernel_image(unsigned long * image_addr,unsigned long * image_size,unsigned long * reserve_addr,unsigned long * reserve_size,efi_loaded_image_t * image,efi_handle_t image_handle)97 efi_status_t handle_kernel_image(unsigned long *image_addr,
98 				 unsigned long *image_size,
99 				 unsigned long *reserve_addr,
100 				 unsigned long *reserve_size,
101 				 efi_loaded_image_t *image,
102 				 efi_handle_t image_handle)
103 {
104 	efi_status_t status;
105 	unsigned long kernel_addr = 0;
106 
107 	kernel_addr = (unsigned long)image->image_base;
108 
109 	status = efi_relocate_kernel(&kernel_addr, kernel_fsize, kernel_asize,
110 		     EFI_KIMG_PREFERRED_ADDRESS, efi_get_kimg_min_align(), 0x0);
111 
112 	*image_addr = kernel_addr;
113 	*image_size = kernel_asize;
114 
115 	return status;
116 }
117 
kernel_entry_address(unsigned long kernel_addr,efi_loaded_image_t * image)118 unsigned long kernel_entry_address(unsigned long kernel_addr,
119 		efi_loaded_image_t *image)
120 {
121 	unsigned long base = (unsigned long)image->image_base;
122 
123 	return (unsigned long)&kernel_entry - base + kernel_addr;
124 }
125