xref: /kvm-unit-tests/lib/efi.c (revision 85c3c524ebe0a64c15acca8f6cff9d91b2c935f5)
1 /*
2  * EFI-related functions to set up and run test cases in EFI
3  *
4  * Copyright (c) 2021, SUSE, Varad Gautam <varad.gautam@suse.com>
5  * Copyright (c) 2021, Google Inc, Zixuan Wang <zixuanwang@google.com>
6  *
7  * SPDX-License-Identifier: LGPL-2.0-or-later
8  */
9 
10 #include "efi.h"
11 #include <argv.h>
12 #include <stdlib.h>
13 #include <ctype.h>
14 #include <libcflat.h>
15 #include <asm/setup.h>
16 
17 /* From lib/argv.c */
18 extern int __argc, __envc;
19 extern char *__argv[100];
20 extern char *__environ[200];
21 
22 extern int main(int argc, char **argv, char **envp);
23 
24 efi_system_table_t *efi_system_table = NULL;
25 
26 static void efi_free_pool(void *ptr)
27 {
28 	efi_bs_call(free_pool, ptr);
29 }
30 
31 efi_status_t efi_get_memory_map(struct efi_boot_memmap *map)
32 {
33 	efi_memory_desc_t *m = NULL;
34 	efi_status_t status;
35 	unsigned long key = 0, map_size = 0, desc_size = 0;
36 	u32 desc_ver;
37 
38 	status = efi_bs_call(get_memory_map, &map_size,
39 			     NULL, &key, &desc_size, &desc_ver);
40 	if (status != EFI_BUFFER_TOO_SMALL || map_size == 0)
41 		goto out;
42 
43 	/*
44 	 * Pad map_size with additional descriptors so we don't need to
45 	 * retry.
46 	 */
47 	map_size += 4 * desc_size;
48 	*map->buff_size = map_size;
49 	status = efi_bs_call(allocate_pool, EFI_LOADER_DATA,
50 			     map_size, (void **)&m);
51 	if (status != EFI_SUCCESS)
52 		goto out;
53 
54 	/* Get the map. */
55 	status = efi_bs_call(get_memory_map, &map_size,
56 			     m, &key, &desc_size, &desc_ver);
57 	if (status != EFI_SUCCESS) {
58 		efi_free_pool(m);
59 		goto out;
60 	}
61 
62 	*map->desc_ver = desc_ver;
63 	*map->desc_size = desc_size;
64 	*map->map_size = map_size;
65 	*map->key_ptr = key;
66 out:
67 	*map->map = m;
68 	return status;
69 }
70 
71 efi_status_t efi_exit_boot_services(void *handle, struct efi_boot_memmap *map)
72 {
73 	return efi_bs_call(exit_boot_services, handle, *map->key_ptr);
74 }
75 
76 efi_status_t efi_get_system_config_table(efi_guid_t table_guid, void **table)
77 {
78 	size_t i;
79 	efi_config_table_t *tables;
80 
81 	tables = (efi_config_table_t *)efi_system_table->tables;
82 	for (i = 0; i < efi_system_table->nr_tables; i++) {
83 		if (!memcmp(&table_guid, &tables[i].guid, sizeof(efi_guid_t))) {
84 			*table = tables[i].table;
85 			return EFI_SUCCESS;
86 		}
87 	}
88 	return EFI_NOT_FOUND;
89 }
90 
91 static void efi_exit(efi_status_t code)
92 {
93 	exit(code);
94 
95 	/*
96 	 * Fallback to UEFI reset_system() service, in case testdev is
97 	 * missing and exit() does not properly exit.
98 	 */
99 	efi_rs_call(reset_system, EFI_RESET_SHUTDOWN, code, 0, NULL);
100 }
101 
102 /* Adapted from drivers/firmware/efi/libstub/efi-stub.c */
103 static char *efi_convert_cmdline(struct efi_loaded_image_64 *image, int *cmd_line_len)
104 {
105 	const u16 *s2;
106 	unsigned long cmdline_addr = 0;
107 	int options_chars = image->load_options_size;
108 	const u16 *options = image->load_options;
109 	int options_bytes = 0, safe_options_bytes = 0;  /* UTF-8 bytes */
110 	bool in_quote = false;
111 	efi_status_t status;
112 	const int COMMAND_LINE_SIZE = 2048;
113 
114 	if (options) {
115 		s2 = options;
116 		while (options_bytes < COMMAND_LINE_SIZE && options_chars--) {
117 			u16 c = *s2++;
118 
119 			if (c < 0x80) {
120 				if (c == L'\0' || c == L'\n')
121 					break;
122 				if (c == L'"')
123 					in_quote = !in_quote;
124 				else if (!in_quote && isspace((char)c))
125 					safe_options_bytes = options_bytes;
126 
127 				options_bytes++;
128 				continue;
129 			}
130 
131 			/*
132 			 * Get the number of UTF-8 bytes corresponding to a
133 			 * UTF-16 character.
134 			 * The first part handles everything in the BMP.
135 			 */
136 			options_bytes += 2 + (c >= 0x800);
137 			/*
138 			 * Add one more byte for valid surrogate pairs. Invalid
139 			 * surrogates will be replaced with 0xfffd and take up
140 			 * only 3 bytes.
141 			 */
142 			if ((c & 0xfc00) == 0xd800) {
143 				/*
144 				 * If the very last word is a high surrogate,
145 				 * we must ignore it since we can't access the
146 				 * low surrogate.
147 				 */
148 				if (!options_chars) {
149 					options_bytes -= 3;
150 				} else if ((*s2 & 0xfc00) == 0xdc00) {
151 					options_bytes++;
152 					options_chars--;
153 					s2++;
154 				}
155 			}
156 		}
157 		if (options_bytes >= COMMAND_LINE_SIZE) {
158 			options_bytes = safe_options_bytes;
159 			printf("Command line is too long: truncated to %d bytes\n",
160 			       options_bytes);
161 		}
162 	}
163 
164 	options_bytes++;        /* NUL termination */
165 
166 	status = efi_bs_call(allocate_pool, EFI_LOADER_DATA, options_bytes, (void **)&cmdline_addr);
167 	if (status != EFI_SUCCESS)
168 		return NULL;
169 
170 	snprintf((char *)cmdline_addr, options_bytes, "%.*ls", options_bytes - 1, options);
171 
172 	*cmd_line_len = options_bytes;
173 	return (char *)cmdline_addr;
174 }
175 
176 efi_status_t efi_main(efi_handle_t handle, efi_system_table_t *sys_tab)
177 {
178 	int ret;
179 	efi_status_t status;
180 	efi_bootinfo_t efi_bootinfo;
181 
182 	efi_system_table = sys_tab;
183 
184 	/* Memory map struct values */
185 	efi_memory_desc_t *map = NULL;
186 	unsigned long map_size = 0, desc_size = 0, key = 0, buff_size = 0;
187 	u32 desc_ver;
188 
189 	/* Helper variables needed to get the cmdline */
190 	struct efi_loaded_image_64 *image;
191 	efi_guid_t loaded_image_proto = LOADED_IMAGE_PROTOCOL_GUID;
192 	char *cmdline_ptr = NULL;
193 	int cmdline_size = 0;
194 
195 	/*
196 	 * Get a handle to the loaded image protocol.  This is used to get
197 	 * information about the running image, such as size and the command
198 	 * line.
199 	 */
200 	status = efi_bs_call(handle_protocol, handle, &loaded_image_proto, (void *)&image);
201 	if (status != EFI_SUCCESS) {
202 		printf("Failed to get loaded image protocol\n");
203 		goto efi_main_error;
204 	}
205 
206 	cmdline_ptr = efi_convert_cmdline(image, &cmdline_size);
207 	if (!cmdline_ptr) {
208 		printf("getting command line via LOADED_IMAGE_PROTOCOL\n");
209 		status = EFI_OUT_OF_RESOURCES;
210 		goto efi_main_error;
211 	}
212 	setup_args(cmdline_ptr);
213 
214 	/* Set up efi_bootinfo */
215 	efi_bootinfo.mem_map.map = &map;
216 	efi_bootinfo.mem_map.map_size = &map_size;
217 	efi_bootinfo.mem_map.desc_size = &desc_size;
218 	efi_bootinfo.mem_map.desc_ver = &desc_ver;
219 	efi_bootinfo.mem_map.key_ptr = &key;
220 	efi_bootinfo.mem_map.buff_size = &buff_size;
221 
222 	/* Get EFI memory map */
223 	status = efi_get_memory_map(&efi_bootinfo.mem_map);
224 	if (status != EFI_SUCCESS) {
225 		printf("Failed to get memory map\n");
226 		goto efi_main_error;
227 	}
228 
229 	/*
230 	 * Exit EFI boot services, let kvm-unit-tests take full control of the
231 	 * guest
232 	 */
233 	status = efi_exit_boot_services(handle, &efi_bootinfo.mem_map);
234 	if (status != EFI_SUCCESS) {
235 		printf("Failed to exit boot services\n");
236 		goto efi_main_error;
237 	}
238 
239 	/* Set up arch-specific resources */
240 	status = setup_efi(&efi_bootinfo);
241 	if (status != EFI_SUCCESS) {
242 		printf("Failed to set up arch-specific resources\n");
243 		goto efi_main_error;
244 	}
245 
246 	/* Run the test case */
247 	ret = main(__argc, __argv, __environ);
248 
249 	/* Shutdown the guest VM */
250 	efi_exit(ret);
251 
252 	/* Unreachable */
253 	return EFI_UNSUPPORTED;
254 
255 efi_main_error:
256 	/* Shutdown the guest with error EFI status */
257 	efi_exit(status);
258 
259 	/* Unreachable */
260 	return EFI_UNSUPPORTED;
261 }
262