blob: 12c66c6ffd1f1588e76cec451e16bc12736f812b [file] [log] [blame]
/*
* EFI-related functions to set up and run test cases in EFI
*
* Copyright (c) 2021, SUSE, Varad Gautam <varad.gautam@suse.com>
* Copyright (c) 2021, Google Inc, Zixuan Wang <zixuanwang@google.com>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include <libcflat.h>
#include <argv.h>
#include <ctype.h>
#include <stdlib.h>
#include <asm/setup.h>
#include "efi.h"
#include "libfdt/libfdt.h"
/* From each arch */
extern char *initrd;
extern u32 initrd_size;
/* From lib/argv.c */
extern int __argc, __envc;
extern char *__argv[100];
extern char *__environ[200];
extern char _text;
extern int main(int argc, char **argv, char **envp);
efi_system_table_t *efi_system_table = NULL;
static void efi_free_pool(void *ptr)
{
efi_bs_call(free_pool, ptr);
}
efi_status_t efi_get_memory_map(struct efi_boot_memmap *map)
{
efi_memory_desc_t *m = NULL;
efi_status_t status;
unsigned long key = 0, map_size = 0, desc_size = 0;
u32 desc_ver;
status = efi_bs_call(get_memory_map, &map_size,
NULL, &key, &desc_size, &desc_ver);
if (status != EFI_BUFFER_TOO_SMALL || map_size == 0)
goto out;
/*
* Pad map_size with additional descriptors so we don't need to
* retry.
*/
map_size += 4 * desc_size;
*map->buff_size = map_size;
status = efi_bs_call(allocate_pool, EFI_LOADER_DATA,
map_size, (void **)&m);
if (status != EFI_SUCCESS)
goto out;
/* Get the map. */
status = efi_bs_call(get_memory_map, &map_size,
m, &key, &desc_size, &desc_ver);
if (status != EFI_SUCCESS) {
efi_free_pool(m);
goto out;
}
*map->desc_ver = desc_ver;
*map->desc_size = desc_size;
*map->map_size = map_size;
*map->key_ptr = key;
out:
*map->map = m;
return status;
}
efi_status_t efi_exit_boot_services(void *handle, struct efi_boot_memmap *map)
{
return efi_bs_call(exit_boot_services, handle, *map->key_ptr);
}
efi_status_t efi_get_system_config_table(efi_guid_t table_guid, void **table)
{
size_t i;
efi_config_table_t *tables;
tables = (efi_config_table_t *)efi_system_table->tables;
for (i = 0; i < efi_system_table->nr_tables; i++) {
if (!memcmp(&table_guid, &tables[i].guid, sizeof(efi_guid_t))) {
*table = tables[i].table;
return EFI_SUCCESS;
}
}
return EFI_NOT_FOUND;
}
static void efi_exit(efi_status_t code)
{
exit(code);
/*
* Fallback to UEFI reset_system() service, in case testdev is
* missing and exit() does not properly exit.
*/
efi_rs_call(reset_system, EFI_RESET_SHUTDOWN, code, 0, NULL);
}
/* Adapted from drivers/firmware/efi/libstub/efi-stub.c */
static char *efi_convert_cmdline(struct efi_loaded_image_64 *image, int *cmd_line_len)
{
const u16 *s2;
unsigned long cmdline_addr = 0;
int options_chars = image->load_options_size;
const u16 *options = image->load_options;
int options_bytes = 0, safe_options_bytes = 0; /* UTF-8 bytes */
bool in_quote = false;
efi_status_t status;
const int COMMAND_LINE_SIZE = 2048;
if (options) {
s2 = options;
while (options_bytes < COMMAND_LINE_SIZE && options_chars--) {
u16 c = *s2++;
if (c < 0x80) {
if (c == L'\0' || c == L'\n')
break;
if (c == L'"')
in_quote = !in_quote;
else if (!in_quote && isspace((char)c))
safe_options_bytes = options_bytes;
options_bytes++;
continue;
}
/*
* Get the number of UTF-8 bytes corresponding to a
* UTF-16 character.
* The first part handles everything in the BMP.
*/
options_bytes += 2 + (c >= 0x800);
/*
* Add one more byte for valid surrogate pairs. Invalid
* surrogates will be replaced with 0xfffd and take up
* only 3 bytes.
*/
if ((c & 0xfc00) == 0xd800) {
/*
* If the very last word is a high surrogate,
* we must ignore it since we can't access the
* low surrogate.
*/
if (!options_chars) {
options_bytes -= 3;
} else if ((*s2 & 0xfc00) == 0xdc00) {
options_bytes++;
options_chars--;
s2++;
}
}
}
if (options_bytes >= COMMAND_LINE_SIZE) {
options_bytes = safe_options_bytes;
printf("Command line is too long: truncated to %d bytes\n",
options_bytes);
}
}
options_bytes++; /* NUL termination */
status = efi_bs_call(allocate_pool, EFI_LOADER_DATA, options_bytes, (void **)&cmdline_addr);
if (status != EFI_SUCCESS)
return NULL;
snprintf((char *)cmdline_addr, options_bytes, "%.*ls", options_bytes - 1, options);
*cmd_line_len = options_bytes;
return (char *)cmdline_addr;
}
/*
* Open the file and read it into a buffer.
*/
static void efi_load_image(efi_handle_t handle, struct efi_loaded_image_64 *image, void **data,
int *datasize, efi_char16_t *path_name)
{
uint64_t buffer_size = sizeof(efi_file_info_t);
efi_file_info_t *file_info;
efi_file_io_interface_t *io_if;
efi_file_t *root, *file;
efi_status_t status;
efi_guid_t file_system_proto_guid = EFI_SIMPLE_FILE_SYSTEM_PROTOCOL_GUID;
efi_guid_t file_info_guid = EFI_FILE_INFO_ID;
/* Open the device */
status = efi_bs_call(handle_protocol, image->device_handle, &file_system_proto_guid,
(void **)&io_if);
if (status != EFI_SUCCESS)
return;
status = io_if->open_volume(io_if, &root);
if (status != EFI_SUCCESS)
return;
/* And then open the file */
status = root->open(root, &file, path_name, EFI_FILE_MODE_READ, 0);
if (status != EFI_SUCCESS) {
printf("Failed to open %ls - %lx\n", path_name, status);
assert(status == EFI_SUCCESS);
}
/* Find the file size in order to allocate the buffer */
status = efi_bs_call(allocate_pool, EFI_LOADER_DATA, buffer_size, (void **)&file_info);
if (status != EFI_SUCCESS)
return;
status = file->get_info(file, &file_info_guid, &buffer_size, file_info);
if (status == EFI_BUFFER_TOO_SMALL) {
efi_free_pool(file_info);
status = efi_bs_call(allocate_pool, EFI_LOADER_DATA, buffer_size, (void **)&file_info);
assert(file_info);
status = file->get_info(file, &file_info_guid, &buffer_size, file_info);
}
assert(status == EFI_SUCCESS);
buffer_size = file_info->file_size;
efi_free_pool(file_info);
status = efi_bs_call(allocate_pool, EFI_LOADER_DATA, buffer_size, (void **)data);
assert(*data);
/* Perform the actual read */
status = file->read(file, &buffer_size, *data);
if (status == EFI_BUFFER_TOO_SMALL) {
efi_free_pool(*data);
status = efi_bs_call(allocate_pool, EFI_LOADER_DATA, buffer_size, (void **)data);
status = file->read(file, &buffer_size, *data);
}
assert(status == EFI_SUCCESS);
*datasize = buffer_size;
}
static int efi_grow_buffer(efi_status_t *status, void **buffer, uint64_t buffer_size)
{
int try_again;
if (!*buffer && buffer_size) {
*status = EFI_BUFFER_TOO_SMALL;
}
try_again = 0;
if (*status == EFI_BUFFER_TOO_SMALL) {
if (*buffer)
efi_free_pool(*buffer);
efi_bs_call(allocate_pool, EFI_LOADER_DATA, buffer_size, buffer);
if (*buffer) {
try_again = 1;
} else {
*status = EFI_OUT_OF_RESOURCES;
}
}
if (!try_again && EFI_ERROR(*status) && *buffer) {
efi_free_pool(*buffer);
*buffer = NULL;
}
return try_again;
}
static void* efi_get_var(efi_handle_t handle, struct efi_loaded_image_64 *image, efi_char16_t *var)
{
efi_status_t status = EFI_SUCCESS;
void *val = NULL;
uint64_t val_size = 100;
efi_guid_t efi_var_guid = EFI_VAR_GUID;
while (efi_grow_buffer(&status, &val, val_size + sizeof(efi_char16_t)))
status = efi_rs_call(get_variable, var, &efi_var_guid, NULL, &val_size, val);
if (val)
((efi_char16_t *)val)[val_size / sizeof(efi_char16_t)] = L'\0';
return val;
}
static void *efi_get_fdt(efi_handle_t handle, struct efi_loaded_image_64 *image)
{
efi_char16_t var[] = ENV_VARNAME_DTBFILE;
efi_char16_t *val;
void *fdt = NULL;
int fdtsize = 0;
val = efi_get_var(handle, image, var);
if (val) {
efi_load_image(handle, image, &fdt, &fdtsize, val);
if (fdtsize == 0)
return NULL;
} else if (efi_get_system_config_table(DEVICE_TREE_GUID, &fdt) != EFI_SUCCESS) {
return NULL;
}
return fdt_check_header(fdt) == 0 ? fdt : NULL;
}
static const struct {
struct efi_vendor_dev_path vendor;
struct efi_generic_dev_path end;
} __packed initrd_dev_path = {
{
{
EFI_DEV_MEDIA,
EFI_DEV_MEDIA_VENDOR,
sizeof(struct efi_vendor_dev_path),
},
LINUX_EFI_INITRD_MEDIA_GUID
}, {
EFI_DEV_END_PATH,
EFI_DEV_END_ENTIRE,
sizeof(struct efi_generic_dev_path)
}
};
static void efi_load_initrd(void)
{
efi_guid_t lf2_proto_guid = EFI_LOAD_FILE2_PROTOCOL_GUID;
efi_device_path_protocol_t *dp;
efi_load_file2_protocol_t *lf2;
efi_handle_t handle;
efi_status_t status;
unsigned long file_size = 0;
initrd = NULL;
initrd_size = 0;
dp = (efi_device_path_protocol_t *)&initrd_dev_path;
status = efi_bs_call(locate_device_path, &lf2_proto_guid, &dp, &handle);
if (status != EFI_SUCCESS)
return;
status = efi_bs_call(handle_protocol, handle, &lf2_proto_guid, (void **)&lf2);
assert(status == EFI_SUCCESS);
status = efi_call_proto(lf2, load_file, dp, false, &file_size, NULL);
assert(status == EFI_BUFFER_TOO_SMALL);
status = efi_bs_call(allocate_pool, EFI_LOADER_DATA, file_size, (void **)&initrd);
assert(status == EFI_SUCCESS);
status = efi_call_proto(lf2, load_file, dp, false, &file_size, (void *)initrd);
assert(status == EFI_SUCCESS);
initrd_size = (u32)file_size;
/*
* UEFI appends initrd=initrd to the command line when an initrd is present.
* Remove it in order to avoid confusing unit tests.
*/
if (!strcmp(__argv[__argc - 1], "initrd=initrd")) {
__argv[__argc - 1] = NULL;
__argc -= 1;
}
}
efi_status_t efi_main(efi_handle_t handle, efi_system_table_t *sys_tab)
{
int ret;
efi_status_t status;
efi_bootinfo_t efi_bootinfo;
efi_system_table = sys_tab;
/* Memory map struct values */
efi_memory_desc_t *map = NULL;
unsigned long map_size = 0, desc_size = 0, key = 0, buff_size = 0;
u32 desc_ver;
/* Helper variables needed to get the cmdline */
struct efi_loaded_image_64 *image;
efi_guid_t loaded_image_proto = LOADED_IMAGE_PROTOCOL_GUID;
char *cmdline_ptr = NULL;
int cmdline_size = 0;
/*
* Get a handle to the loaded image protocol. This is used to get
* information about the running image, such as size and the command
* line.
*/
status = efi_bs_call(handle_protocol, handle, &loaded_image_proto, (void *)&image);
if (status != EFI_SUCCESS) {
printf("Failed to get loaded image protocol\n");
goto efi_main_error;
}
cmdline_ptr = efi_convert_cmdline(image, &cmdline_size);
if (!cmdline_ptr) {
printf("getting command line via LOADED_IMAGE_PROTOCOL\n");
status = EFI_OUT_OF_RESOURCES;
goto efi_main_error;
}
setup_args(cmdline_ptr);
efi_load_initrd();
efi_bootinfo.fdt = efi_get_fdt(handle, image);
/* Set up efi_bootinfo */
efi_bootinfo.mem_map.map = &map;
efi_bootinfo.mem_map.map_size = &map_size;
efi_bootinfo.mem_map.desc_size = &desc_size;
efi_bootinfo.mem_map.desc_ver = &desc_ver;
efi_bootinfo.mem_map.key_ptr = &key;
efi_bootinfo.mem_map.buff_size = &buff_size;
/* Get EFI memory map */
status = efi_get_memory_map(&efi_bootinfo.mem_map);
if (status != EFI_SUCCESS) {
printf("Failed to get memory map\n");
goto efi_main_error;
}
/*
* Exit EFI boot services, let kvm-unit-tests take full control of the
* guest
*/
status = efi_exit_boot_services(handle, &efi_bootinfo.mem_map);
if (status != EFI_SUCCESS) {
printf("Failed to exit boot services\n");
goto efi_main_error;
}
/* Set up arch-specific resources */
status = setup_efi(&efi_bootinfo);
if (status != EFI_SUCCESS) {
printf("Failed to set up arch-specific resources\n");
goto efi_main_error;
}
printf("Address of image is: 0x%lx\n", (unsigned long)&_text);
/* Run the test case */
ret = main(__argc, __argv, __environ);
/* Shutdown the guest VM */
efi_exit(ret);
/* Unreachable */
return EFI_UNSUPPORTED;
efi_main_error:
/* Shutdown the guest with error EFI status */
efi_exit(status);
/* Unreachable */
return EFI_UNSUPPORTED;
}