diff options
Diffstat (limited to 'lib/efi_loader')
-rw-r--r-- | lib/efi_loader/Makefile | 8 | ||||
-rw-r--r-- | lib/efi_loader/efi_bootmgr.c | 180 | ||||
-rw-r--r-- | lib/efi_loader/efi_boottime.c | 263 | ||||
-rw-r--r-- | lib/efi_loader/efi_console.c | 127 | ||||
-rw-r--r-- | lib/efi_loader/efi_device_path.c | 563 | ||||
-rw-r--r-- | lib/efi_loader/efi_device_path_to_text.c | 242 | ||||
-rw-r--r-- | lib/efi_loader/efi_disk.c | 88 | ||||
-rw-r--r-- | lib/efi_loader/efi_file.c | 560 | ||||
-rw-r--r-- | lib/efi_loader/efi_image_loader.c | 26 | ||||
-rw-r--r-- | lib/efi_loader/efi_memory.c | 5 | ||||
-rw-r--r-- | lib/efi_loader/efi_net.c | 24 | ||||
-rw-r--r-- | lib/efi_loader/efi_runtime.c | 23 | ||||
-rw-r--r-- | lib/efi_loader/efi_variable.c | 335 |
13 files changed, 2239 insertions, 205 deletions
diff --git a/lib/efi_loader/Makefile b/lib/efi_loader/Makefile index 30bf343a36..ddb978f650 100644 --- a/lib/efi_loader/Makefile +++ b/lib/efi_loader/Makefile @@ -10,12 +10,14 @@ CFLAGS_helloworld.o := $(CFLAGS_EFI) CFLAGS_REMOVE_helloworld.o := $(CFLAGS_NON_EFI) -efiprogs-$(CONFIG_CMD_BOOTEFI_HELLO_COMPILE) += helloworld.efi -always := $(efiprogs-y) +ifneq ($(CONFIG_CMD_BOOTEFI_HELLO_COMPILE),) +always += helloworld.efi +endif obj-$(CONFIG_CMD_BOOTEFI_HELLO) += helloworld_efi.o obj-y += efi_image_loader.o efi_boottime.o efi_runtime.o efi_console.o -obj-y += efi_memory.o efi_device_path_to_text.o +obj-y += efi_memory.o efi_device_path_to_text.o efi_device_path.o +obj-y += efi_file.o efi_variable.o efi_bootmgr.o obj-$(CONFIG_LCD) += efi_gop.o obj-$(CONFIG_DM_VIDEO) += efi_gop.o obj-$(CONFIG_PARTITIONS) += efi_disk.o diff --git a/lib/efi_loader/efi_bootmgr.c b/lib/efi_loader/efi_bootmgr.c new file mode 100644 index 0000000000..857d88a879 --- /dev/null +++ b/lib/efi_loader/efi_bootmgr.c @@ -0,0 +1,180 @@ +/* + * EFI utils + * + * Copyright (c) 2017 Rob Clark + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +#include <common.h> +#include <charset.h> +#include <malloc.h> +#include <efi_loader.h> + +static const struct efi_boot_services *bs; +static const struct efi_runtime_services *rs; + +#define LOAD_OPTION_ACTIVE 0x00000001 +#define LOAD_OPTION_FORCE_RECONNECT 0x00000002 +#define LOAD_OPTION_HIDDEN 0x00000008 + +/* + * bootmgr implements the logic of trying to find a payload to boot + * based on the BootOrder + BootXXXX variables, and then loading it. + * + * TODO detecting a special key held (f9?) and displaying a boot menu + * like you would get on a PC would be clever. + * + * TODO if we had a way to write and persist variables after the OS + * has started, we'd also want to check OsIndications to see if we + * should do normal or recovery boot. + */ + + +/* + * See section 3.1.3 in the v2.7 UEFI spec for more details on + * the layout of EFI_LOAD_OPTION. In short it is: + * + * typedef struct _EFI_LOAD_OPTION { + * UINT32 Attributes; + * UINT16 FilePathListLength; + * // CHAR16 Description[]; <-- variable length, NULL terminated + * // EFI_DEVICE_PATH_PROTOCOL FilePathList[]; <-- FilePathListLength bytes + * // UINT8 OptionalData[]; + * } EFI_LOAD_OPTION; + */ +struct load_option { + u32 attributes; + u16 file_path_length; + u16 *label; + struct efi_device_path *file_path; + u8 *optional_data; +}; + +/* parse an EFI_LOAD_OPTION, as described above */ +static void parse_load_option(struct load_option *lo, void *ptr) +{ + lo->attributes = *(u32 *)ptr; + ptr += sizeof(u32); + + lo->file_path_length = *(u16 *)ptr; + ptr += sizeof(u16); + + lo->label = ptr; + ptr += (utf16_strlen(lo->label) + 1) * 2; + + lo->file_path = ptr; + ptr += lo->file_path_length; + + lo->optional_data = ptr; +} + +/* free() the result */ +static void *get_var(u16 *name, const efi_guid_t *vendor, + unsigned long *size) +{ + efi_guid_t *v = (efi_guid_t *)vendor; + efi_status_t ret; + void *buf = NULL; + + *size = 0; + EFI_CALL(ret = rs->get_variable((s16 *)name, v, NULL, size, buf)); + if (ret == EFI_BUFFER_TOO_SMALL) { + buf = malloc(*size); + EFI_CALL(ret = rs->get_variable((s16 *)name, v, NULL, size, buf)); + } + + if (ret != EFI_SUCCESS) { + free(buf); + *size = 0; + return NULL; + } + + return buf; +} + +/* + * Attempt to load load-option number 'n', returning device_path and file_path + * if successful. This checks that the EFI_LOAD_OPTION is active (enabled) + * and that the specified file to boot exists. + */ +static void *try_load_entry(uint16_t n, struct efi_device_path **device_path, + struct efi_device_path **file_path) +{ + struct load_option lo; + u16 varname[] = L"Boot0000"; + u16 hexmap[] = L"0123456789ABCDEF"; + void *load_option, *image = NULL; + unsigned long size; + + varname[4] = hexmap[(n & 0xf000) >> 12]; + varname[5] = hexmap[(n & 0x0f00) >> 8]; + varname[6] = hexmap[(n & 0x00f0) >> 4]; + varname[7] = hexmap[(n & 0x000f) >> 0]; + + load_option = get_var(varname, &efi_global_variable_guid, &size); + if (!load_option) + return NULL; + + parse_load_option(&lo, load_option); + + if (lo.attributes & LOAD_OPTION_ACTIVE) { + efi_status_t ret; + u16 *str = NULL; + + debug("%s: trying to load \"%ls\" from: %ls\n", __func__, + lo.label, (str = efi_dp_str(lo.file_path))); + efi_free_pool(str); + + ret = efi_load_image_from_path(lo.file_path, &image); + + if (ret != EFI_SUCCESS) + goto error; + + printf("Booting: %ls\n", lo.label); + efi_dp_split_file_path(lo.file_path, device_path, file_path); + } + +error: + free(load_option); + + return image; +} + +/* + * Attempt to load, in the order specified by BootOrder EFI variable, the + * available load-options, finding and returning the first one that can + * be loaded successfully. + */ +void *efi_bootmgr_load(struct efi_device_path **device_path, + struct efi_device_path **file_path) +{ + uint16_t *bootorder; + unsigned long size; + void *image = NULL; + int i, num; + + __efi_entry_check(); + + bs = systab.boottime; + rs = systab.runtime; + + bootorder = get_var(L"BootOrder", &efi_global_variable_guid, &size); + if (!bootorder) + goto error; + + num = size / sizeof(uint16_t); + for (i = 0; i < num; i++) { + debug("%s: trying to load Boot%04X\n", __func__, bootorder[i]); + image = try_load_entry(bootorder[i], device_path, file_path); + if (image) + break; + } + + free(bootorder); + +error: + __efi_exit_check(); + + return image; +} diff --git a/lib/efi_loader/efi_boottime.c b/lib/efi_loader/efi_boottime.c index 43f32385fa..9e741c3cf3 100644 --- a/lib/efi_loader/efi_boottime.c +++ b/lib/efi_loader/efi_boottime.c @@ -8,6 +8,7 @@ #include <common.h> #include <efi_loader.h> +#include <environment.h> #include <malloc.h> #include <asm/global_data.h> #include <libfdt_env.h> @@ -18,6 +19,9 @@ DECLARE_GLOBAL_DATA_PTR; +/* Task priority level */ +static UINTN efi_tpl = TPL_APPLICATION; + /* This list contains all the EFI objects our payload has access to */ LIST_HEAD(efi_obj_list); @@ -109,6 +113,11 @@ static const char *indent_string(int level) return &indent[max - level]; } +const char *__efi_nesting(void) +{ + return indent_string(nesting_level); +} + const char *__efi_nesting_inc(void) { return indent_string(nesting_level++); @@ -154,12 +163,15 @@ static u64 efi_div10(u64 a) void efi_signal_event(struct efi_event *event) { - if (event->signaled) - return; - event->signaled = 1; - if (event->type & EVT_NOTIFY_SIGNAL) { - EFI_CALL(event->notify_function(event, event->notify_context)); + if (event->notify_function) { + event->queued = 1; + /* Check TPL */ + if (efi_tpl >= event->notify_tpl) + return; + EFI_CALL_VOID(event->notify_function(event, + event->notify_context)); } + event->queued = 0; } static efi_status_t efi_unsupported(const char *funcname) @@ -170,14 +182,31 @@ static efi_status_t efi_unsupported(const char *funcname) static unsigned long EFIAPI efi_raise_tpl(UINTN new_tpl) { + UINTN old_tpl = efi_tpl; + EFI_ENTRY("0x%zx", new_tpl); - return EFI_EXIT(0); + + if (new_tpl < efi_tpl) + debug("WARNING: new_tpl < current_tpl in %s\n", __func__); + efi_tpl = new_tpl; + if (efi_tpl > TPL_HIGH_LEVEL) + efi_tpl = TPL_HIGH_LEVEL; + + EFI_EXIT(EFI_SUCCESS); + return old_tpl; } static void EFIAPI efi_restore_tpl(UINTN old_tpl) { EFI_ENTRY("0x%zx", old_tpl); - efi_unsupported(__func__); + + if (old_tpl > efi_tpl) + debug("WARNING: old_tpl > current_tpl in %s\n", __func__); + efi_tpl = old_tpl; + if (efi_tpl > TPL_HIGH_LEVEL) + efi_tpl = TPL_HIGH_LEVEL; + + EFI_EXIT(EFI_SUCCESS); } static efi_status_t EFIAPI efi_allocate_pages_ext(int type, int memory_type, @@ -270,6 +299,7 @@ efi_status_t efi_create_event(uint32_t type, UINTN notify_tpl, efi_events[i].notify_context = notify_context; /* Disable timers on bootup */ efi_events[i].trigger_next = -1ULL; + efi_events[i].queued = 0; efi_events[i].signaled = 0; *event = &efi_events[i]; return EFI_SUCCESS; @@ -301,16 +331,25 @@ void efi_timer_check(void) u64 now = timer_get_us(); for (i = 0; i < ARRAY_SIZE(efi_events); ++i) { - if (!efi_events[i].type || - !(efi_events[i].type & EVT_TIMER) || - efi_events[i].trigger_type == EFI_TIMER_STOP || + if (!efi_events[i].type) + continue; + if (efi_events[i].queued) + efi_signal_event(&efi_events[i]); + if (!(efi_events[i].type & EVT_TIMER) || now < efi_events[i].trigger_next) continue; - if (efi_events[i].trigger_type == EFI_TIMER_PERIODIC) { + switch (efi_events[i].trigger_type) { + case EFI_TIMER_RELATIVE: + efi_events[i].trigger_type = EFI_TIMER_STOP; + break; + case EFI_TIMER_PERIODIC: efi_events[i].trigger_next += efi_events[i].trigger_time; - efi_events[i].signaled = 0; + break; + default: + continue; } + efi_events[i].signaled = 1; efi_signal_event(&efi_events[i]); } WATCHDOG_RESET(); @@ -347,6 +386,7 @@ efi_status_t efi_set_timer(struct efi_event *event, enum efi_timer_delay type, } event->trigger_type = type; event->trigger_time = trigger_time; + event->signaled = 0; return EFI_SUCCESS; } return EFI_INVALID_PARAMETER; @@ -371,6 +411,9 @@ static efi_status_t EFIAPI efi_wait_for_event(unsigned long num_events, /* Check parameters */ if (!num_events || !event) return EFI_EXIT(EFI_INVALID_PARAMETER); + /* Check TPL */ + if (efi_tpl != TPL_APPLICATION) + return EFI_EXIT(EFI_UNSUPPORTED); for (i = 0; i < num_events; ++i) { for (j = 0; j < ARRAY_SIZE(efi_events); ++j) { if (event[i] == &efi_events[j]) @@ -380,6 +423,8 @@ static efi_status_t EFIAPI efi_wait_for_event(unsigned long num_events, known_event: if (!event[i]->type || event[i]->type & EVT_NOTIFY_SIGNAL) return EFI_EXIT(EFI_INVALID_PARAMETER); + if (!event[i]->signaled) + efi_signal_event(event[i]); } /* Wait for signal */ @@ -412,7 +457,11 @@ static efi_status_t EFIAPI efi_signal_event_ext(struct efi_event *event) for (i = 0; i < ARRAY_SIZE(efi_events); ++i) { if (event != &efi_events[i]) continue; - efi_signal_event(event); + if (event->signaled) + break; + event->signaled = 1; + if (event->type & EVT_NOTIFY_SIGNAL) + efi_signal_event(event); break; } return EFI_EXIT(EFI_SUCCESS); @@ -427,6 +476,7 @@ static efi_status_t EFIAPI efi_close_event(struct efi_event *event) if (event == &efi_events[i]) { event->type = 0; event->trigger_next = -1ULL; + event->queued = 0; event->signaled = 0; return EFI_EXIT(EFI_SUCCESS); } @@ -445,6 +495,8 @@ static efi_status_t EFIAPI efi_check_event(struct efi_event *event) continue; if (!event->type || event->type & EVT_NOTIFY_SIGNAL) break; + if (!event->signaled) + efi_signal_event(event); if (event->signaled) return EFI_EXIT(EFI_SUCCESS); return EFI_EXIT(EFI_NOT_READY); @@ -513,7 +565,7 @@ static efi_status_t EFIAPI efi_install_protocol_interface_ext(void **handle, efi_guid_t *protocol, int protocol_interface_type, void *protocol_interface) { - EFI_ENTRY("%p, %p, %d, %p", handle, protocol, protocol_interface_type, + EFI_ENTRY("%p, %pUl, %d, %p", handle, protocol, protocol_interface_type, protocol_interface); return EFI_EXIT(efi_install_protocol_interface(handle, protocol, @@ -525,7 +577,7 @@ static efi_status_t EFIAPI efi_reinstall_protocol_interface(void *handle, efi_guid_t *protocol, void *old_interface, void *new_interface) { - EFI_ENTRY("%p, %p, %p, %p", handle, protocol, old_interface, + EFI_ENTRY("%p, %pUl, %p, %p", handle, protocol, old_interface, new_interface); return EFI_EXIT(EFI_ACCESS_DENIED); } @@ -574,7 +626,7 @@ out: static efi_status_t EFIAPI efi_uninstall_protocol_interface_ext(void *handle, efi_guid_t *protocol, void *protocol_interface) { - EFI_ENTRY("%p, %p, %p", handle, protocol, protocol_interface); + EFI_ENTRY("%p, %pUl, %p", handle, protocol, protocol_interface); return EFI_EXIT(efi_uninstall_protocol_interface(handle, protocol, protocol_interface)); @@ -584,7 +636,7 @@ static efi_status_t EFIAPI efi_register_protocol_notify(efi_guid_t *protocol, struct efi_event *event, void **registration) { - EFI_ENTRY("%p, %p, %p", protocol, event, registration); + EFI_ENTRY("%pUl, %p, %p", protocol, event, registration); return EFI_EXIT(EFI_OUT_OF_RESOURCES); } @@ -654,7 +706,7 @@ static efi_status_t EFIAPI efi_locate_handle_ext( efi_guid_t *protocol, void *search_key, unsigned long *buffer_size, efi_handle_t *buffer) { - EFI_ENTRY("%d, %p, %p, %p, %p", search_type, protocol, search_key, + EFI_ENTRY("%d, %pUl, %p, %p, %p", search_type, protocol, search_key, buffer_size, buffer); return EFI_EXIT(efi_locate_handle(search_type, protocol, search_key, @@ -665,8 +717,17 @@ static efi_status_t EFIAPI efi_locate_device_path(efi_guid_t *protocol, struct efi_device_path **device_path, efi_handle_t *device) { - EFI_ENTRY("%p, %p, %p", protocol, device_path, device); - return EFI_EXIT(EFI_NOT_FOUND); + struct efi_object *efiobj; + + EFI_ENTRY("%pUl, %p, %p", protocol, device_path, device); + + efiobj = efi_dp_find_obj(*device_path, device_path); + if (!efiobj) + return EFI_EXIT(EFI_NOT_FOUND); + + *device = efiobj->handle; + + return EFI_EXIT(EFI_SUCCESS); } /* Collapses configuration table entries, removing index i */ @@ -713,10 +774,87 @@ efi_status_t efi_install_configuration_table(const efi_guid_t *guid, void *table static efi_status_t EFIAPI efi_install_configuration_table_ext(efi_guid_t *guid, void *table) { - EFI_ENTRY("%p, %p", guid, table); + EFI_ENTRY("%pUl, %p", guid, table); return EFI_EXIT(efi_install_configuration_table(guid, table)); } +/* Initialize a loaded_image_info + loaded_image_info object with correct + * protocols, boot-device, etc. + */ +void efi_setup_loaded_image(struct efi_loaded_image *info, struct efi_object *obj, + struct efi_device_path *device_path, + struct efi_device_path *file_path) +{ + obj->handle = info; + + /* + * When asking for the device path interface, return + * bootefi_device_path + */ + obj->protocols[0].guid = &efi_guid_device_path; + obj->protocols[0].protocol_interface = device_path; + + /* + * When asking for the loaded_image interface, just + * return handle which points to loaded_image_info + */ + obj->protocols[1].guid = &efi_guid_loaded_image; + obj->protocols[1].protocol_interface = info; + + obj->protocols[2].guid = &efi_guid_console_control; + obj->protocols[2].protocol_interface = (void *)&efi_console_control; + + obj->protocols[3].guid = &efi_guid_device_path_to_text_protocol; + obj->protocols[3].protocol_interface = + (void *)&efi_device_path_to_text; + + info->file_path = file_path; + info->device_handle = efi_dp_find_obj(device_path, NULL); + + list_add_tail(&obj->link, &efi_obj_list); +} + +efi_status_t efi_load_image_from_path(struct efi_device_path *file_path, + void **buffer) +{ + struct efi_file_info *info = NULL; + struct efi_file_handle *f; + static efi_status_t ret; + uint64_t bs; + + f = efi_file_from_path(file_path); + if (!f) + return EFI_DEVICE_ERROR; + + bs = 0; + EFI_CALL(ret = f->getinfo(f, (efi_guid_t *)&efi_file_info_guid, + &bs, info)); + if (ret == EFI_BUFFER_TOO_SMALL) { + info = malloc(bs); + EFI_CALL(ret = f->getinfo(f, (efi_guid_t *)&efi_file_info_guid, + &bs, info)); + } + if (ret != EFI_SUCCESS) + goto error; + + ret = efi_allocate_pool(EFI_LOADER_DATA, info->file_size, buffer); + if (ret) + goto error; + + EFI_CALL(ret = f->read(f, &info->file_size, *buffer)); + +error: + free(info); + EFI_CALL(f->close(f)); + + if (ret != EFI_SUCCESS) { + efi_free_pool(*buffer); + *buffer = NULL; + } + + return ret; +} + static efi_status_t EFIAPI efi_load_image(bool boot_policy, efi_handle_t parent_image, struct efi_device_path *file_path, @@ -724,25 +862,40 @@ static efi_status_t EFIAPI efi_load_image(bool boot_policy, unsigned long source_size, efi_handle_t *image_handle) { - static struct efi_object loaded_image_info_obj = { - .protocols = { - { - .guid = &efi_guid_loaded_image, - }, - }, - }; struct efi_loaded_image *info; struct efi_object *obj; EFI_ENTRY("%d, %p, %p, %p, %ld, %p", boot_policy, parent_image, file_path, source_buffer, source_size, image_handle); - info = malloc(sizeof(*info)); - loaded_image_info_obj.protocols[0].protocol_interface = info; - obj = malloc(sizeof(loaded_image_info_obj)); - memset(info, 0, sizeof(*info)); - memcpy(obj, &loaded_image_info_obj, sizeof(loaded_image_info_obj)); - obj->handle = info; - info->file_path = file_path; + + info = calloc(1, sizeof(*info)); + obj = calloc(1, sizeof(*obj)); + + if (!source_buffer) { + struct efi_device_path *dp, *fp; + efi_status_t ret; + + ret = efi_load_image_from_path(file_path, &source_buffer); + if (ret != EFI_SUCCESS) { + free(info); + free(obj); + return EFI_EXIT(ret); + } + + /* + * split file_path which contains both the device and + * file parts: + */ + efi_dp_split_file_path(file_path, &dp, &fp); + + efi_setup_loaded_image(info, obj, dp, fp); + } else { + /* In this case, file_path is the "device" path, ie. + * something like a HARDWARE_DEVICE:MEMORY_MAPPED + */ + efi_setup_loaded_image(info, obj, file_path, NULL); + } + info->reserved = efi_load_pe(source_buffer, info); if (!info->reserved) { free(info); @@ -751,7 +904,6 @@ static efi_status_t EFIAPI efi_load_image(bool boot_policy, } *image_handle = info; - list_add_tail(&obj->link, &efi_obj_list); return EFI_EXIT(EFI_SUCCESS); } @@ -793,6 +945,15 @@ static efi_status_t EFIAPI efi_exit(efi_handle_t image_handle, EFI_ENTRY("%p, %ld, %ld, %p", image_handle, exit_status, exit_data_size, exit_data); + /* Make sure entry/exit counts for EFI world cross-overs match */ + __efi_exit_check(); + + /* + * But longjmp out with the U-Boot gd, not the application's, as + * the other end is a setjmp call inside EFI context. + */ + efi_restore_gd(); + loaded_image_info->exit_status = exit_status; longjmp(&loaded_image_info->exit_jmp, 1); @@ -840,8 +1001,24 @@ static void efi_exit_caches(void) static efi_status_t EFIAPI efi_exit_boot_services(void *image_handle, unsigned long map_key) { + int i; + EFI_ENTRY("%p, %ld", image_handle, map_key); + /* Notify that ExitBootServices is invoked. */ + for (i = 0; i < ARRAY_SIZE(efi_events); ++i) { + if (efi_events[i].type != EVT_SIGNAL_EXIT_BOOT_SERVICES) + continue; + efi_signal_event(&efi_events[i]); + } + /* Make sure that notification functions are not called anymore */ + efi_tpl = TPL_HIGH_LEVEL; + +#if defined(CONFIG_CMD_SAVEENV) && !defined(CONFIG_ENV_IS_NOWHERE) + /* save any EFI variables that have been written: */ + env_save(); +#endif + board_quiesce_devices(); /* Fix up caches for EFI payloads if necessary */ @@ -906,7 +1083,7 @@ static efi_status_t EFIAPI efi_close_protocol(void *handle, void *agent_handle, void *controller_handle) { - EFI_ENTRY("%p, %p, %p, %p", handle, protocol, agent_handle, + EFI_ENTRY("%p, %pUl, %p, %p", handle, protocol, agent_handle, controller_handle); return EFI_EXIT(EFI_NOT_FOUND); } @@ -916,7 +1093,7 @@ static efi_status_t EFIAPI efi_open_protocol_information(efi_handle_t handle, struct efi_open_protocol_info_entry **entry_buffer, unsigned long *entry_count) { - EFI_ENTRY("%p, %p, %p, %p", handle, protocol, entry_buffer, + EFI_ENTRY("%p, %pUl, %p, %p", handle, protocol, entry_buffer, entry_count); return EFI_EXIT(EFI_NOT_FOUND); } @@ -982,7 +1159,7 @@ static efi_status_t EFIAPI efi_locate_handle_buffer( efi_status_t r; unsigned long buffer_size = 0; - EFI_ENTRY("%d, %p, %p, %p, %p", search_type, protocol, search_key, + EFI_ENTRY("%d, %pUl, %p, %p, %p", search_type, protocol, search_key, no_handles, buffer); if (!no_handles || !buffer) { @@ -1014,11 +1191,13 @@ static efi_status_t EFIAPI efi_locate_protocol(efi_guid_t *protocol, struct list_head *lhandle; int i; - EFI_ENTRY("%p, %p, %p", protocol, registration, protocol_interface); + EFI_ENTRY("%pUl, %p, %p", protocol, registration, protocol_interface); if (!protocol || !protocol_interface) return EFI_EXIT(EFI_INVALID_PARAMETER); + EFI_PRINT_GUID("protocol", protocol); + list_for_each(lhandle, &efi_obj_list) { struct efi_object *efiobj; @@ -1122,7 +1301,7 @@ static efi_status_t EFIAPI efi_open_protocol( int i; efi_status_t r = EFI_INVALID_PARAMETER; - EFI_ENTRY("%p, %p, %p, %p, %p, 0x%x", handle, protocol, + EFI_ENTRY("%p, %pUl, %p, %p, %p, 0x%x", handle, protocol, protocol_interface, agent_handle, controller_handle, attributes); @@ -1132,6 +1311,8 @@ static efi_status_t EFIAPI efi_open_protocol( goto out; } + EFI_PRINT_GUID("protocol", protocol); + switch (attributes) { case EFI_OPEN_PROTOCOL_BY_HANDLE_PROTOCOL: case EFI_OPEN_PROTOCOL_GET_PROTOCOL: diff --git a/lib/efi_loader/efi_console.c b/lib/efi_loader/efi_console.c index 3fc82b8726..fd5398d61d 100644 --- a/lib/efi_loader/efi_console.c +++ b/lib/efi_loader/efi_console.c @@ -8,7 +8,10 @@ #include <common.h> #include <charset.h> +#include <dm/device.h> #include <efi_loader.h> +#include <stdio_dev.h> +#include <video_console.h> static bool console_size_queried; @@ -137,34 +140,46 @@ static efi_status_t EFIAPI efi_cout_reset( return EFI_EXIT(EFI_UNSUPPORTED); } -static void print_unicode_in_utf8(u16 c) -{ - char utf8[MAX_UTF8_PER_UTF16] = { 0 }; - utf16_to_utf8((u8 *)utf8, &c, 1); - puts(utf8); -} - static efi_status_t EFIAPI efi_cout_output_string( struct efi_simple_text_output_protocol *this, - const unsigned short *string) + const efi_string_t string) { - struct cout_mode *mode; - u16 ch; + struct simple_text_output_mode *con = &efi_con_mode; + struct cout_mode *mode = &efi_cout_modes[con->mode]; - mode = &efi_cout_modes[efi_con_mode.mode]; EFI_ENTRY("%p, %p", this, string); - for (;(ch = *string); string++) { - print_unicode_in_utf8(ch); - efi_con_mode.cursor_column++; - if (ch == '\n') { - efi_con_mode.cursor_column = 1; - efi_con_mode.cursor_row++; - } else if (efi_con_mode.cursor_column > mode->columns) { - efi_con_mode.cursor_column = 1; - efi_con_mode.cursor_row++; + + unsigned int n16 = utf16_strlen(string); + char buf[MAX_UTF8_PER_UTF16 * n16 + 1]; + char *p; + + *utf16_to_utf8((u8 *)buf, string, n16) = '\0'; + + fputs(stdout, buf); + + for (p = buf; *p; p++) { + switch (*p) { + case '\r': /* carriage-return */ + con->cursor_column = 0; + break; + case '\n': /* newline */ + con->cursor_column = 0; + con->cursor_row++; + break; + case '\t': /* tab, assume 8 char align */ + break; + case '\b': /* backspace */ + con->cursor_column = max(0, con->cursor_column - 1); + break; + default: + con->cursor_column++; + break; } - if (efi_con_mode.cursor_row > mode->rows) - efi_con_mode.cursor_row = mode->rows; + if (con->cursor_column >= mode->columns) { + con->cursor_column = 0; + con->cursor_row++; + } + con->cursor_row = min(con->cursor_row, (s32)mode->rows - 1); } return EFI_EXIT(EFI_SUCCESS); @@ -172,7 +187,7 @@ static efi_status_t EFIAPI efi_cout_output_string( static efi_status_t EFIAPI efi_cout_test_string( struct efi_simple_text_output_protocol *this, - const unsigned short *string) + const efi_string_t string) { EFI_ENTRY("%p, %p", this, string); return EFI_EXIT(EFI_SUCCESS); @@ -186,6 +201,34 @@ static bool cout_mode_matches(struct cout_mode *mode, int rows, int cols) return (mode->rows == rows) && (mode->columns == cols); } +static int query_console_serial(int *rows, int *cols) +{ + /* Ask the terminal about its size */ + int n[3]; + u64 timeout; + + /* Empty input buffer */ + while (tstc()) + getc(); + + printf(ESC"[18t"); + + /* Check if we have a terminal that understands */ + timeout = timer_get_us() + 1000000; + while (!tstc()) + if (timer_get_us() > timeout) + return -1; + + /* Read {depth,rows,cols} */ + if (term_read_reply(n, 3, 't')) + return -1; + + *cols = n[2]; + *rows = n[1]; + + return 0; +} + static efi_status_t EFIAPI efi_cout_query_mode( struct efi_simple_text_output_protocol *this, unsigned long mode_number, unsigned long *columns, @@ -194,34 +237,24 @@ static efi_status_t EFIAPI efi_cout_query_mode( EFI_ENTRY("%p, %ld, %p, %p", this, mode_number, columns, rows); if (!console_size_queried) { - /* Ask the terminal about its size */ - int n[3]; - int cols; - int rows; - u64 timeout; + const char *stdout_name = env_get("stdout"); + int rows, cols; console_size_queried = true; - /* Empty input buffer */ - while (tstc()) - getc(); - - printf(ESC"[18t"); - - /* Check if we have a terminal that understands */ - timeout = timer_get_us() + 1000000; - while (!tstc()) - if (timer_get_us() > timeout) - goto out; - - /* Read {depth,rows,cols} */ - if (term_read_reply(n, 3, 't')) { + if (stdout_name && !strcmp(stdout_name, "vidconsole") && + IS_ENABLED(CONFIG_DM_VIDEO)) { + struct stdio_dev *stdout_dev = + stdio_get_by_name("vidconsole"); + struct udevice *dev = stdout_dev->priv; + struct vidconsole_priv *priv = + dev_get_uclass_priv(dev); + rows = priv->rows; + cols = priv->cols; + } else if (query_console_serial(&rows, &cols)) { goto out; } - cols = n[2]; - rows = n[1]; - /* Test if we can have Mode 1 */ if (cols >= 80 && rows >= 50) { efi_cout_modes[1].present = 1; @@ -426,8 +459,10 @@ static void EFIAPI efi_console_timer_notify(struct efi_event *event, void *context) { EFI_ENTRY("%p, %p", event, context); - if (tstc()) + if (tstc()) { + efi_con_in.wait_for_key->signaled = 1; efi_signal_event(efi_con_in.wait_for_key); + } EFI_EXIT(EFI_SUCCESS); } diff --git a/lib/efi_loader/efi_device_path.c b/lib/efi_loader/efi_device_path.c new file mode 100644 index 0000000000..5d5c3b3464 --- /dev/null +++ b/lib/efi_loader/efi_device_path.c @@ -0,0 +1,563 @@ +/* + * EFI device path from u-boot device-model mapping + * + * (C) Copyright 2017 Rob Clark + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +#include <common.h> +#include <blk.h> +#include <dm.h> +#include <usb.h> +#include <mmc.h> +#include <efi_loader.h> +#include <inttypes.h> +#include <part.h> + +/* template END node: */ +static const struct efi_device_path END = { + .type = DEVICE_PATH_TYPE_END, + .sub_type = DEVICE_PATH_SUB_TYPE_END, + .length = sizeof(END), +}; + +#define U_BOOT_GUID \ + EFI_GUID(0xe61d73b9, 0xa384, 0x4acc, \ + 0xae, 0xab, 0x82, 0xe8, 0x28, 0xf3, 0x62, 0x8b) + +/* template ROOT node: */ +static const struct efi_device_path_vendor ROOT = { + .dp = { + .type = DEVICE_PATH_TYPE_HARDWARE_DEVICE, + .sub_type = DEVICE_PATH_SUB_TYPE_VENDOR, + .length = sizeof(ROOT), + }, + .guid = U_BOOT_GUID, +}; + +static void *dp_alloc(size_t sz) +{ + void *buf; + + if (efi_allocate_pool(EFI_ALLOCATE_ANY_PAGES, sz, &buf) != EFI_SUCCESS) + return NULL; + + return buf; +} + +/* + * Iterate to next block in device-path, terminating (returning NULL) + * at /End* node. + */ +struct efi_device_path *efi_dp_next(const struct efi_device_path *dp) +{ + if (dp == NULL) + return NULL; + if (dp->type == DEVICE_PATH_TYPE_END) + return NULL; + dp = ((void *)dp) + dp->length; + if (dp->type == DEVICE_PATH_TYPE_END) + return NULL; + return (struct efi_device_path *)dp; +} + +/* + * Compare two device-paths, stopping when the shorter of the two hits + * an End* node. This is useful to, for example, compare a device-path + * representing a device with one representing a file on the device, or + * a device with a parent device. + */ +int efi_dp_match(struct efi_device_path *a, struct efi_device_path *b) +{ + while (1) { + int ret; + + ret = memcmp(&a->length, &b->length, sizeof(a->length)); + if (ret) + return ret; + + ret = memcmp(a, b, a->length); + if (ret) + return ret; + + a = efi_dp_next(a); + b = efi_dp_next(b); + + if (!a || !b) + return 0; + } +} + + +/* + * See UEFI spec (section 3.1.2, about short-form device-paths.. + * tl;dr: we can have a device-path that starts with a USB WWID + * or USB Class node, and a few other cases which don't encode + * the full device path with bus hierarchy: + * + * - MESSAGING:USB_WWID + * - MESSAGING:USB_CLASS + * - MEDIA:FILE_PATH + * - MEDIA:HARD_DRIVE + * - MESSAGING:URI + */ +static struct efi_device_path *shorten_path(struct efi_device_path *dp) +{ + while (dp) { + /* + * TODO: Add MESSAGING:USB_WWID and MESSAGING:URI.. + * in practice fallback.efi just uses MEDIA:HARD_DRIVE + * so not sure when we would see these other cases. + */ + if (EFI_DP_TYPE(dp, MESSAGING_DEVICE, MSG_USB_CLASS) || + EFI_DP_TYPE(dp, MEDIA_DEVICE, HARD_DRIVE_PATH) || + EFI_DP_TYPE(dp, MEDIA_DEVICE, FILE_PATH)) + return dp; + + dp = efi_dp_next(dp); + } + + return dp; +} + +static struct efi_object *find_obj(struct efi_device_path *dp, bool short_path, + struct efi_device_path **rem) +{ + struct efi_object *efiobj; + + list_for_each_entry(efiobj, &efi_obj_list, link) { + int i; + + for (i = 0; i < ARRAY_SIZE(efiobj->protocols); i++) { + struct efi_handler *handler = &efiobj->protocols[i]; + struct efi_device_path *obj_dp; + + if (!handler->guid) + break; + + if (guidcmp(handler->guid, &efi_guid_device_path)) + continue; + + obj_dp = handler->protocol_interface; + + do { + if (efi_dp_match(dp, obj_dp) == 0) { + if (rem) { + *rem = ((void *)dp) + + efi_dp_size(obj_dp); + } + return efiobj; + } + + obj_dp = shorten_path(efi_dp_next(obj_dp)); + } while (short_path && obj_dp); + } + } + + return NULL; +} + + +/* + * Find an efiobj from device-path, if 'rem' is not NULL, returns the + * remaining part of the device path after the matched object. + */ +struct efi_object *efi_dp_find_obj(struct efi_device_path *dp, + struct efi_device_path **rem) +{ + struct efi_object *efiobj; + + efiobj = find_obj(dp, false, rem); + + if (!efiobj) + efiobj = find_obj(dp, true, rem); + + return efiobj; +} + +/* return size not including End node: */ +unsigned efi_dp_size(const struct efi_device_path *dp) +{ + unsigned sz = 0; + + while (dp) { + sz += dp->length; + dp = efi_dp_next(dp); + } + + return sz; +} + +struct efi_device_path *efi_dp_dup(const struct efi_device_path *dp) +{ + struct efi_device_path *ndp; + unsigned sz = efi_dp_size(dp) + sizeof(END); + + if (!dp) + return NULL; + + ndp = dp_alloc(sz); + memcpy(ndp, dp, sz); + + return ndp; +} + +struct efi_device_path *efi_dp_append(const struct efi_device_path *dp1, + const struct efi_device_path *dp2) +{ + struct efi_device_path *ret; + + if (!dp1) { + ret = efi_dp_dup(dp2); + } else if (!dp2) { + ret = efi_dp_dup(dp1); + } else { + /* both dp1 and dp2 are non-null */ + unsigned sz1 = efi_dp_size(dp1); + unsigned sz2 = efi_dp_size(dp2); + void *p = dp_alloc(sz1 + sz2 + sizeof(END)); + memcpy(p, dp1, sz1); + memcpy(p + sz1, dp2, sz2); + memcpy(p + sz1 + sz2, &END, sizeof(END)); + ret = p; + } + + return ret; +} + +struct efi_device_path *efi_dp_append_node(const struct efi_device_path *dp, + const struct efi_device_path *node) +{ + struct efi_device_path *ret; + + if (!node && !dp) { + ret = efi_dp_dup(&END); + } else if (!node) { + ret = efi_dp_dup(dp); + } else if (!dp) { + unsigned sz = node->length; + void *p = dp_alloc(sz + sizeof(END)); + memcpy(p, node, sz); + memcpy(p + sz, &END, sizeof(END)); + ret = p; + } else { + /* both dp and node are non-null */ + unsigned sz = efi_dp_size(dp); + void *p = dp_alloc(sz + node->length + sizeof(END)); + memcpy(p, dp, sz); + memcpy(p + sz, node, node->length); + memcpy(p + sz + node->length, &END, sizeof(END)); + ret = p; + } + + return ret; +} + +#ifdef CONFIG_DM +/* size of device-path not including END node for device and all parents + * up to the root device. + */ +static unsigned dp_size(struct udevice *dev) +{ + if (!dev || !dev->driver) + return sizeof(ROOT); + + switch (dev->driver->id) { + case UCLASS_ROOT: + case UCLASS_SIMPLE_BUS: + /* stop traversing parents at this point: */ + return sizeof(ROOT); + case UCLASS_MMC: + return dp_size(dev->parent) + + sizeof(struct efi_device_path_sd_mmc_path); + case UCLASS_MASS_STORAGE: + case UCLASS_USB_HUB: + return dp_size(dev->parent) + + sizeof(struct efi_device_path_usb_class); + default: + /* just skip over unknown classes: */ + return dp_size(dev->parent); + } +} + +static void *dp_fill(void *buf, struct udevice *dev) +{ + if (!dev || !dev->driver) + return buf; + + switch (dev->driver->id) { + case UCLASS_ROOT: + case UCLASS_SIMPLE_BUS: { + /* stop traversing parents at this point: */ + struct efi_device_path_vendor *vdp = buf; + *vdp = ROOT; + return &vdp[1]; + } +#if defined(CONFIG_DM_MMC) && defined(CONFIG_MMC) + case UCLASS_MMC: { + struct efi_device_path_sd_mmc_path *sddp = + dp_fill(buf, dev->parent); + struct mmc *mmc = mmc_get_mmc_dev(dev); + struct blk_desc *desc = mmc_get_blk_desc(mmc); + + sddp->dp.type = DEVICE_PATH_TYPE_MESSAGING_DEVICE; + sddp->dp.sub_type = (desc->if_type == IF_TYPE_MMC) ? + DEVICE_PATH_SUB_TYPE_MSG_MMC : + DEVICE_PATH_SUB_TYPE_MSG_SD; + sddp->dp.length = sizeof(*sddp); + sddp->slot_number = dev->seq; + + return &sddp[1]; + } +#endif + case UCLASS_MASS_STORAGE: + case UCLASS_USB_HUB: { + struct efi_device_path_usb_class *udp = + dp_fill(buf, dev->parent); + struct usb_device *udev = dev_get_parent_priv(dev); + struct usb_device_descriptor *desc = &udev->descriptor; + + udp->dp.type = DEVICE_PATH_TYPE_MESSAGING_DEVICE; + udp->dp.sub_type = DEVICE_PATH_SUB_TYPE_MSG_USB_CLASS; + udp->dp.length = sizeof(*udp); + udp->vendor_id = desc->idVendor; + udp->product_id = desc->idProduct; + udp->device_class = desc->bDeviceClass; + udp->device_subclass = desc->bDeviceSubClass; + udp->device_protocol = desc->bDeviceProtocol; + + return &udp[1]; + } + default: + debug("unhandled device class: %s (%u)\n", + dev->name, dev->driver->id); + return dp_fill(buf, dev->parent); + } +} + +/* Construct a device-path from a device: */ +struct efi_device_path *efi_dp_from_dev(struct udevice *dev) +{ + void *buf, *start; + + start = buf = dp_alloc(dp_size(dev) + sizeof(END)); + buf = dp_fill(buf, dev); + *((struct efi_device_path *)buf) = END; + + return start; +} +#endif + +static unsigned dp_part_size(struct blk_desc *desc, int part) +{ + unsigned dpsize; + +#ifdef CONFIG_BLK + dpsize = dp_size(desc->bdev->parent); +#else + dpsize = sizeof(ROOT) + sizeof(struct efi_device_path_usb); +#endif + + if (part == 0) /* the actual disk, not a partition */ + return dpsize; + + if (desc->part_type == PART_TYPE_ISO) + dpsize += sizeof(struct efi_device_path_cdrom_path); + else + dpsize += sizeof(struct efi_device_path_hard_drive_path); + + return dpsize; +} + +static void *dp_part_fill(void *buf, struct blk_desc *desc, int part) +{ + disk_partition_t info; + +#ifdef CONFIG_BLK + buf = dp_fill(buf, desc->bdev->parent); +#else + /* + * We *could* make a more accurate path, by looking at if_type + * and handling all the different cases like we do for non- + * legacy (ie CONFIG_BLK=y) case. But most important thing + * is just to have a unique device-path for if_type+devnum. + * So map things to a fictional USB device: + */ + struct efi_device_path_usb *udp; + + memcpy(buf, &ROOT, sizeof(ROOT)); + buf += sizeof(ROOT); + + udp = buf; + udp->dp.type = DEVICE_PATH_TYPE_MESSAGING_DEVICE; + udp->dp.sub_type = DEVICE_PATH_SUB_TYPE_MSG_USB; + udp->dp.length = sizeof(*udp); + udp->parent_port_number = desc->if_type; + udp->usb_interface = desc->devnum; + buf = &udp[1]; +#endif + + if (part == 0) /* the actual disk, not a partition */ + return buf; + + part_get_info(desc, part, &info); + + if (desc->part_type == PART_TYPE_ISO) { + struct efi_device_path_cdrom_path *cddp = buf; + + cddp->boot_entry = part - 1; + cddp->dp.type = DEVICE_PATH_TYPE_MEDIA_DEVICE; + cddp->dp.sub_type = DEVICE_PATH_SUB_TYPE_CDROM_PATH; + cddp->dp.length = sizeof(*cddp); + cddp->partition_start = info.start; + cddp->partition_end = info.size; + + buf = &cddp[1]; + } else { + struct efi_device_path_hard_drive_path *hddp = buf; + + hddp->dp.type = DEVICE_PATH_TYPE_MEDIA_DEVICE; + hddp->dp.sub_type = DEVICE_PATH_SUB_TYPE_HARD_DRIVE_PATH; + hddp->dp.length = sizeof(*hddp); + hddp->partition_number = part - 1; + hddp->partition_start = info.start; + hddp->partition_end = info.size; + if (desc->part_type == PART_TYPE_EFI) + hddp->partmap_type = 2; + else + hddp->partmap_type = 1; + hddp->signature_type = desc->sig_type; + if (hddp->signature_type != 0) + memcpy(hddp->partition_signature, &desc->guid_sig, + sizeof(hddp->partition_signature)); + + buf = &hddp[1]; + } + + return buf; +} + + +/* Construct a device-path from a partition on a blk device: */ +struct efi_device_path *efi_dp_from_part(struct blk_desc *desc, int part) +{ + void *buf, *start; + + start = buf = dp_alloc(dp_part_size(desc, part) + sizeof(END)); + + buf = dp_part_fill(buf, desc, part); + + *((struct efi_device_path *)buf) = END; + + return start; +} + +/* convert path to an UEFI style path (ie. DOS style backslashes and utf16) */ +static void path_to_uefi(u16 *uefi, const char *path) +{ + while (*path) { + char c = *(path++); + if (c == '/') + c = '\\'; + *(uefi++) = c; + } + *uefi = '\0'; +} + +/* + * If desc is NULL, this creates a path with only the file component, + * otherwise it creates a full path with both device and file components + */ +struct efi_device_path *efi_dp_from_file(struct blk_desc *desc, int part, + const char *path) +{ + struct efi_device_path_file_path *fp; + void *buf, *start; + unsigned dpsize = 0, fpsize; + + if (desc) + dpsize = dp_part_size(desc, part); + + fpsize = sizeof(struct efi_device_path) + 2 * (strlen(path) + 1); + dpsize += fpsize; + + start = buf = dp_alloc(dpsize + sizeof(END)); + + if (desc) + buf = dp_part_fill(buf, desc, part); + + /* add file-path: */ + fp = buf; + fp->dp.type = DEVICE_PATH_TYPE_MEDIA_DEVICE; + fp->dp.sub_type = DEVICE_PATH_SUB_TYPE_FILE_PATH; + fp->dp.length = fpsize; + path_to_uefi(fp->str, path); + buf += fpsize; + + *((struct efi_device_path *)buf) = END; + + return start; +} + +#ifdef CONFIG_NET +struct efi_device_path *efi_dp_from_eth(void) +{ + struct efi_device_path_mac_addr *ndp; + void *buf, *start; + unsigned dpsize = 0; + + assert(eth_get_dev()); + +#ifdef CONFIG_DM_ETH + dpsize += dp_size(eth_get_dev()); +#else + dpsize += sizeof(ROOT); +#endif + dpsize += sizeof(*ndp); + + start = buf = dp_alloc(dpsize + sizeof(END)); + +#ifdef CONFIG_DM_ETH + buf = dp_fill(buf, eth_get_dev()); +#else + memcpy(buf, &ROOT, sizeof(ROOT)); + buf += sizeof(ROOT); +#endif + + ndp = buf; + ndp->dp.type = DEVICE_PATH_TYPE_MESSAGING_DEVICE; + ndp->dp.sub_type = DEVICE_PATH_SUB_TYPE_MSG_MAC_ADDR; + ndp->dp.length = sizeof(*ndp); + memcpy(ndp->mac.addr, eth_get_ethaddr(), ARP_HLEN); + buf = &ndp[1]; + + *((struct efi_device_path *)buf) = END; + + return start; +} +#endif + +/* + * Helper to split a full device path (containing both device and file + * parts) into it's constituent parts. + */ +void efi_dp_split_file_path(struct efi_device_path *full_path, + struct efi_device_path **device_path, + struct efi_device_path **file_path) +{ + struct efi_device_path *p, *dp, *fp; + + dp = efi_dp_dup(full_path); + p = dp; + while (!EFI_DP_TYPE(p, MEDIA_DEVICE, FILE_PATH)) + p = efi_dp_next(p); + fp = efi_dp_dup(p); + + p->type = DEVICE_PATH_TYPE_END; + p->sub_type = DEVICE_PATH_SUB_TYPE_END; + p->length = sizeof(*p); + + *device_path = dp; + *file_path = fp; +} diff --git a/lib/efi_loader/efi_device_path_to_text.c b/lib/efi_loader/efi_device_path_to_text.c index 4b2f43f0c8..1a5ef3919b 100644 --- a/lib/efi_loader/efi_device_path_to_text.c +++ b/lib/efi_loader/efi_device_path_to_text.c @@ -15,81 +15,199 @@ const efi_guid_t efi_guid_device_path_to_text_protocol = EFI_DEVICE_PATH_TO_TEXT_PROTOCOL_GUID; -static uint16_t *efi_convert_device_node_to_text( - struct efi_device_path_protocol *device_node, - bool display_only, - bool allow_shortcuts) +static char *dp_unknown(char *s, struct efi_device_path *dp) { - unsigned long buffer_size; - efi_status_t r; - uint16_t *buffer = NULL; - int i; + s += sprintf(s, "/UNKNOWN(%04x,%04x)", dp->type, dp->sub_type); + return s; +} - switch (device_node->type) { - case DEVICE_PATH_TYPE_END: - return NULL; - case DEVICE_PATH_TYPE_MESSAGING_DEVICE: - switch (device_node->sub_type) { - case DEVICE_PATH_SUB_TYPE_MSG_MAC_ADDR: { - struct efi_device_path_mac_addr *dp = - (struct efi_device_path_mac_addr *)device_node; - - if (dp->if_type != 0 && dp->if_type != 1) - break; - r = efi_allocate_pool(EFI_ALLOCATE_ANY_PAGES, - 2 * MAC_OUTPUT_LEN, - (void **)&buffer); - if (r != EFI_SUCCESS) - return NULL; - sprintf((char *)buffer, - "MAC(%02x%02x%02x%02x%02x%02x,0x%1x)", - dp->mac.addr[0], dp->mac.addr[1], - dp->mac.addr[2], dp->mac.addr[3], - dp->mac.addr[4], dp->mac.addr[5], - dp->if_type); - for (i = MAC_OUTPUT_LEN - 1; i >= 0; --i) - buffer[i] = ((uint8_t *)buffer)[i]; +static char *dp_hardware(char *s, struct efi_device_path *dp) +{ + switch (dp->sub_type) { + case DEVICE_PATH_SUB_TYPE_VENDOR: { + struct efi_device_path_vendor *vdp = + (struct efi_device_path_vendor *)dp; + s += sprintf(s, "/VenHw(%pUl)", &vdp->guid); + break; + } + default: + s = dp_unknown(s, dp); + break; + } + return s; +} + +static char *dp_acpi(char *s, struct efi_device_path *dp) +{ + switch (dp->sub_type) { + case DEVICE_PATH_SUB_TYPE_ACPI_DEVICE: { + struct efi_device_path_acpi_path *adp = + (struct efi_device_path_acpi_path *)dp; + s += sprintf(s, "/Acpi(PNP%04x", EISA_PNP_NUM(adp->hid)); + if (adp->uid) + s += sprintf(s, ",%d", adp->uid); + s += sprintf(s, ")"); + break; + } + default: + s = dp_unknown(s, dp); + break; + } + return s; +} + +static char *dp_msging(char *s, struct efi_device_path *dp) +{ + switch (dp->sub_type) { + case DEVICE_PATH_SUB_TYPE_MSG_USB: { + struct efi_device_path_usb *udp = + (struct efi_device_path_usb *)dp; + s += sprintf(s, "/Usb(0x%x,0x%x)", udp->parent_port_number, + udp->usb_interface); + break; + } + case DEVICE_PATH_SUB_TYPE_MSG_MAC_ADDR: { + struct efi_device_path_mac_addr *mdp = + (struct efi_device_path_mac_addr *)dp; + + if (mdp->if_type != 0 && mdp->if_type != 1) break; - } - } + + s += sprintf(s, "/MAC(%02x%02x%02x%02x%02x%02x,0x%1x)", + mdp->mac.addr[0], mdp->mac.addr[1], + mdp->mac.addr[2], mdp->mac.addr[3], + mdp->mac.addr[4], mdp->mac.addr[5], + mdp->if_type); + + break; + } + case DEVICE_PATH_SUB_TYPE_MSG_USB_CLASS: { + struct efi_device_path_usb_class *ucdp = + (struct efi_device_path_usb_class *)dp; + + s += sprintf(s, "/USBClass(%x,%x,%x,%x,%x)", + ucdp->vendor_id, ucdp->product_id, + ucdp->device_class, ucdp->device_subclass, + ucdp->device_protocol); + + break; + } + case DEVICE_PATH_SUB_TYPE_MSG_SD: + case DEVICE_PATH_SUB_TYPE_MSG_MMC: { + const char *typename = + (dp->sub_type == DEVICE_PATH_SUB_TYPE_MSG_SD) ? + "SDCard" : "MMC"; + struct efi_device_path_sd_mmc_path *sddp = + (struct efi_device_path_sd_mmc_path *)dp; + s += sprintf(s, "/%s(Slot%u)", typename, sddp->slot_number); + break; + } + default: + s = dp_unknown(s, dp); break; - case DEVICE_PATH_TYPE_MEDIA_DEVICE: - switch (device_node->sub_type) { - case DEVICE_PATH_SUB_TYPE_FILE_PATH: - buffer_size = device_node->length - 4; - r = efi_allocate_pool(EFI_ALLOCATE_ANY_PAGES, - buffer_size, (void **) &buffer); - if (r != EFI_SUCCESS) - return NULL; - memcpy(buffer, device_node->data, buffer_size); + } + return s; +} + +static char *dp_media(char *s, struct efi_device_path *dp) +{ + switch (dp->sub_type) { + case DEVICE_PATH_SUB_TYPE_HARD_DRIVE_PATH: { + struct efi_device_path_hard_drive_path *hddp = + (struct efi_device_path_hard_drive_path *)dp; + void *sig = hddp->partition_signature; + + switch (hddp->signature_type) { + case SIG_TYPE_MBR: + s += sprintf(s, "/HD(Part%d,Sig%08x)", + hddp->partition_number, + *(uint32_t *)sig); break; + case SIG_TYPE_GUID: + s += sprintf(s, "/HD(Part%d,Sig%pUl)", + hddp->partition_number, sig); + default: + s += sprintf(s, "/HD(Part%d,MBRType=%02x,SigType=%02x)", + hddp->partition_number, hddp->partmap_type, + hddp->signature_type); } + + break; + } + case DEVICE_PATH_SUB_TYPE_CDROM_PATH: { + struct efi_device_path_cdrom_path *cddp = + (struct efi_device_path_cdrom_path *)dp; + s += sprintf(s, "/CDROM(0x%x)", cddp->boot_entry); + break; + } + case DEVICE_PATH_SUB_TYPE_FILE_PATH: { + struct efi_device_path_file_path *fp = + (struct efi_device_path_file_path *)dp; + int slen = (dp->length - sizeof(*dp)) / 2; + s += sprintf(s, "/%-*ls", slen, fp->str); break; } + default: + s = dp_unknown(s, dp); + break; + } + return s; +} - /* - * For all node types that we do not yet support return - * 'UNKNOWN(type,subtype)'. - */ - if (!buffer) { - r = efi_allocate_pool(EFI_ALLOCATE_ANY_PAGES, - 2 * UNKNOWN_OUTPUT_LEN, - (void **)&buffer); - if (r != EFI_SUCCESS) - return NULL; - sprintf((char *)buffer, - "UNKNOWN(%04x,%04x)", - device_node->type, - device_node->sub_type); - for (i = UNKNOWN_OUTPUT_LEN - 1; i >= 0; --i) - buffer[i] = ((uint8_t *)buffer)[i]; +static uint16_t *efi_convert_device_node_to_text( + struct efi_device_path *dp, + bool display_only, + bool allow_shortcuts) +{ + unsigned long len; + efi_status_t r; + char buf[512]; /* this ought be be big enough for worst case */ + char *str = buf; + uint16_t *out; + + while (dp) { + switch (dp->type) { + case DEVICE_PATH_TYPE_HARDWARE_DEVICE: + str = dp_hardware(str, dp); + break; + case DEVICE_PATH_TYPE_ACPI_DEVICE: + str = dp_acpi(str, dp); + break; + case DEVICE_PATH_TYPE_MESSAGING_DEVICE: + str = dp_msging(str, dp); + break; + case DEVICE_PATH_TYPE_MEDIA_DEVICE: + str = dp_media(str, dp); + break; + default: + str = dp_unknown(str, dp); + } + + dp = efi_dp_next(dp); } - return buffer; + *str++ = '\0'; + + len = str - buf; + r = efi_allocate_pool(EFI_ALLOCATE_ANY_PAGES, 2 * len, (void **)&out); + if (r != EFI_SUCCESS) + return NULL; + + ascii2unicode(out, buf); + out[len - 1] = 0; + + return out; } +/* helper for debug prints.. efi_free_pool() the result. */ +uint16_t *efi_dp_str(struct efi_device_path *dp) +{ + return efi_convert_device_node_to_text(dp, true, true); +} + + static uint16_t EFIAPI *efi_convert_device_node_to_text_ext( - struct efi_device_path_protocol *device_node, + struct efi_device_path *device_node, bool display_only, bool allow_shortcuts) { @@ -105,7 +223,7 @@ static uint16_t EFIAPI *efi_convert_device_node_to_text_ext( } static uint16_t EFIAPI *efi_convert_device_path_to_text( - struct efi_device_path_protocol *device_path, + struct efi_device_path *device_path, bool display_only, bool allow_shortcuts) { diff --git a/lib/efi_loader/efi_disk.c b/lib/efi_loader/efi_disk.c index ed06485e33..eb9ce772d1 100644 --- a/lib/efi_loader/efi_disk.c +++ b/lib/efi_loader/efi_disk.c @@ -28,11 +28,15 @@ struct efi_disk_obj { /* EFI Interface Media descriptor struct, referenced by ops */ struct efi_block_io_media media; /* EFI device path to this block device */ - struct efi_device_path_file_path *dp; + struct efi_device_path *dp; + /* partition # */ + unsigned int part; + /* handle to filesys proto (for partition objects) */ + struct efi_simple_file_system_protocol *volume; /* Offset into disk for simple partitions */ lbaint_t offset; /* Internal block device */ - const struct blk_desc *desc; + struct blk_desc *desc; }; static efi_status_t EFIAPI efi_disk_reset(struct efi_block_io *this, @@ -47,7 +51,7 @@ enum efi_disk_direction { EFI_DISK_WRITE, }; -static efi_status_t EFIAPI efi_disk_rw_blocks(struct efi_block_io *this, +static efi_status_t efi_disk_rw_blocks(struct efi_block_io *this, u32 media_id, u64 lba, unsigned long buffer_size, void *buffer, enum efi_disk_direction direction) { @@ -170,28 +174,58 @@ static const struct efi_block_io block_io_disk_template = { .flush_blocks = &efi_disk_flush_blocks, }; +/* + * Find filesystem from a device-path. The passed in path 'p' probably + * contains one or more /File(name) nodes, so the comparison stops at + * the first /File() node, and returns the pointer to that via 'rp'. + * This is mostly intended to be a helper to map a device-path to an + * efi_file_handle object. + */ +struct efi_simple_file_system_protocol * +efi_fs_from_path(struct efi_device_path *fp) +{ + struct efi_object *efiobj; + struct efi_disk_obj *diskobj; + + efiobj = efi_dp_find_obj(fp, NULL); + if (!efiobj) + return NULL; + + diskobj = container_of(efiobj, struct efi_disk_obj, parent); + + return diskobj->volume; +} + static void efi_disk_add_dev(const char *name, const char *if_typename, - const struct blk_desc *desc, + struct blk_desc *desc, int dev_index, - lbaint_t offset) + lbaint_t offset, + unsigned int part) { struct efi_disk_obj *diskobj; - struct efi_device_path_file_path *dp; - int objlen = sizeof(*diskobj) + (sizeof(*dp) * 2); /* Don't add empty devices */ if (!desc->lba) return; - diskobj = calloc(1, objlen); + diskobj = calloc(1, sizeof(*diskobj)); /* Fill in object data */ - dp = (void *)&diskobj[1]; + diskobj->dp = efi_dp_from_part(desc, part); + diskobj->part = part; diskobj->parent.protocols[0].guid = &efi_block_io_guid; diskobj->parent.protocols[0].protocol_interface = &diskobj->ops; diskobj->parent.protocols[1].guid = &efi_guid_device_path; - diskobj->parent.protocols[1].protocol_interface = dp; + diskobj->parent.protocols[1].protocol_interface = diskobj->dp; + if (part >= 1) { + diskobj->volume = efi_simple_file_system(desc, part, + diskobj->dp); + diskobj->parent.protocols[2].guid = + &efi_simple_file_system_protocol_guid; + diskobj->parent.protocols[2].protocol_interface = + diskobj->volume; + } diskobj->parent.handle = diskobj; diskobj->ops = block_io_disk_template; diskobj->ifname = if_typename; @@ -207,17 +241,6 @@ static void efi_disk_add_dev(const char *name, diskobj->media.last_block = desc->lba - offset; diskobj->ops.media = &diskobj->media; - /* Fill in device path */ - diskobj->dp = dp; - dp[0].dp.type = DEVICE_PATH_TYPE_MEDIA_DEVICE; - dp[0].dp.sub_type = DEVICE_PATH_SUB_TYPE_FILE_PATH; - dp[0].dp.length = sizeof(*dp); - ascii2unicode(dp[0].str, name); - - dp[1].dp.type = DEVICE_PATH_TYPE_END; - dp[1].dp.sub_type = DEVICE_PATH_SUB_TYPE_END; - dp[1].dp.length = sizeof(*dp); - /* Hook up to the device list */ list_add_tail(&diskobj->parent.link, &efi_obj_list); } @@ -236,14 +259,18 @@ static int efi_disk_create_eltorito(struct blk_desc *desc, if (desc->part_type != PART_TYPE_ISO) return 0; + /* and devices for each partition: */ while (!part_get_info(desc, part, &info)) { snprintf(devname, sizeof(devname), "%s:%d", pdevname, part); efi_disk_add_dev(devname, if_typename, desc, diskid, - info.start); + info.start, part); part++; disks++; } + + /* ... and add block device: */ + efi_disk_add_dev(devname, if_typename, desc, diskid, 0, 0); #endif return disks; @@ -271,9 +298,22 @@ int efi_disk_register(void) uclass_next_device_check(&dev)) { struct blk_desc *desc = dev_get_uclass_platdata(dev); const char *if_typename = dev->driver->name; + disk_partition_t info; + int part = 1; printf("Scanning disk %s...\n", dev->name); - efi_disk_add_dev(dev->name, if_typename, desc, desc->devnum, 0); + + /* add devices for each partition: */ + while (!part_get_info(desc, part, &info)) { + efi_disk_add_dev(dev->name, if_typename, desc, + desc->devnum, 0, part); + part++; + } + + /* ... and add block device: */ + efi_disk_add_dev(dev->name, if_typename, desc, + desc->devnum, 0, 0); + disks++; /* @@ -309,7 +349,7 @@ int efi_disk_register(void) snprintf(devname, sizeof(devname), "%s%d", if_typename, i); - efi_disk_add_dev(devname, if_typename, desc, i, 0); + efi_disk_add_dev(devname, if_typename, desc, i, 0, 0); disks++; /* diff --git a/lib/efi_loader/efi_file.c b/lib/efi_loader/efi_file.c new file mode 100644 index 0000000000..52a4e7438e --- /dev/null +++ b/lib/efi_loader/efi_file.c @@ -0,0 +1,560 @@ +/* + * EFI utils + * + * Copyright (c) 2017 Rob Clark + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +#include <common.h> +#include <charset.h> +#include <efi_loader.h> +#include <malloc.h> +#include <fs.h> + +struct file_system { + struct efi_simple_file_system_protocol base; + struct efi_device_path *dp; + struct blk_desc *desc; + int part; +}; +#define to_fs(x) container_of(x, struct file_system, base) + +struct file_handle { + struct efi_file_handle base; + struct file_system *fs; + loff_t offset; /* current file position/cursor */ + int isdir; + + /* for reading a directory: */ + struct fs_dir_stream *dirs; + struct fs_dirent *dent; + + char path[0]; +}; +#define to_fh(x) container_of(x, struct file_handle, base) + +static const struct efi_file_handle efi_file_handle_protocol; + +static char *basename(struct file_handle *fh) +{ + char *s = strrchr(fh->path, '/'); + if (s) + return s + 1; + return fh->path; +} + +static int set_blk_dev(struct file_handle *fh) +{ + return fs_set_blk_dev_with_part(fh->fs->desc, fh->fs->part); +} + +static int is_dir(struct file_handle *fh) +{ + struct fs_dir_stream *dirs; + + set_blk_dev(fh); + dirs = fs_opendir(fh->path); + if (!dirs) + return 0; + + fs_closedir(dirs); + + return 1; +} + +/* + * Normalize a path which may include either back or fwd slashes, + * double slashes, . or .. entries in the path, etc. + */ +static int sanitize_path(char *path) +{ + char *p; + + /* backslash to slash: */ + p = path; + while ((p = strchr(p, '\\'))) + *p++ = '/'; + + /* handle double-slashes: */ + p = path; + while ((p = strstr(p, "//"))) { + char *src = p + 1; + memmove(p, src, strlen(src) + 1); + } + + /* handle extra /.'s */ + p = path; + while ((p = strstr(p, "/."))) { + /* + * You'd be tempted to do this *after* handling ".."s + * below to avoid having to check if "/." is start of + * a "/..", but that won't have the correct results.. + * for example, "/foo/./../bar" would get resolved to + * "/foo/bar" if you did these two passes in the other + * order + */ + if (p[2] == '.') { + p += 2; + continue; + } + char *src = p + 2; + memmove(p, src, strlen(src) + 1); + } + + /* handle extra /..'s: */ + p = path; + while ((p = strstr(p, "/.."))) { + char *src = p + 3; + + p--; + + /* find beginning of previous path entry: */ + while (true) { + if (p < path) + return -1; + if (*p == '/') + break; + p--; + } + + memmove(p, src, strlen(src) + 1); + } + + return 0; +} + +/* NOTE: despite what you would expect, 'file_name' is actually a path. + * With windoze style backlashes, ofc. + */ +static struct efi_file_handle *file_open(struct file_system *fs, + struct file_handle *parent, s16 *file_name, u64 mode) +{ + struct file_handle *fh; + char f0[MAX_UTF8_PER_UTF16] = {0}; + int plen = 0; + int flen = 0; + + if (file_name) { + utf16_to_utf8((u8 *)f0, (u16 *)file_name, 1); + flen = utf16_strlen((u16 *)file_name); + } + + /* we could have a parent, but also an absolute path: */ + if (f0[0] == '\\') { + plen = 0; + } else if (parent) { + plen = strlen(parent->path) + 1; + } + + /* +2 is for null and '/' */ + fh = calloc(1, sizeof(*fh) + plen + (flen * MAX_UTF8_PER_UTF16) + 2); + + fh->base = efi_file_handle_protocol; + fh->fs = fs; + + if (parent) { + char *p = fh->path; + + if (plen > 0) { + strcpy(p, parent->path); + p += plen - 1; + *p++ = '/'; + } + + utf16_to_utf8((u8 *)p, (u16 *)file_name, flen); + + if (sanitize_path(fh->path)) + goto error; + + /* check if file exists: */ + if (set_blk_dev(fh)) + goto error; + + if (!((mode & EFI_FILE_MODE_CREATE) || fs_exists(fh->path))) + goto error; + + /* figure out if file is a directory: */ + fh->isdir = is_dir(fh); + } else { + fh->isdir = 1; + strcpy(fh->path, ""); + } + + return &fh->base; + +error: + free(fh); + return NULL; +} + +static efi_status_t EFIAPI efi_file_open(struct efi_file_handle *file, + struct efi_file_handle **new_handle, + s16 *file_name, u64 open_mode, u64 attributes) +{ + struct file_handle *fh = to_fh(file); + + EFI_ENTRY("%p, %p, \"%ls\", %llx, %llu", file, new_handle, file_name, + open_mode, attributes); + + *new_handle = file_open(fh->fs, fh, file_name, open_mode); + if (!*new_handle) + return EFI_EXIT(EFI_NOT_FOUND); + + return EFI_EXIT(EFI_SUCCESS); +} + +static efi_status_t file_close(struct file_handle *fh) +{ + fs_closedir(fh->dirs); + free(fh); + return EFI_SUCCESS; +} + +static efi_status_t EFIAPI efi_file_close(struct efi_file_handle *file) +{ + struct file_handle *fh = to_fh(file); + EFI_ENTRY("%p", file); + return EFI_EXIT(file_close(fh)); +} + +static efi_status_t EFIAPI efi_file_delete(struct efi_file_handle *file) +{ + struct file_handle *fh = to_fh(file); + EFI_ENTRY("%p", file); + file_close(fh); + return EFI_EXIT(EFI_WARN_DELETE_FAILURE); +} + +static efi_status_t file_read(struct file_handle *fh, u64 *buffer_size, + void *buffer) +{ + loff_t actread; + + if (fs_read(fh->path, (ulong)buffer, fh->offset, + *buffer_size, &actread)) + return EFI_DEVICE_ERROR; + + *buffer_size = actread; + fh->offset += actread; + + return EFI_SUCCESS; +} + +static efi_status_t dir_read(struct file_handle *fh, u64 *buffer_size, + void *buffer) +{ + struct efi_file_info *info = buffer; + struct fs_dirent *dent; + unsigned int required_size; + + if (!fh->dirs) { + assert(fh->offset == 0); + fh->dirs = fs_opendir(fh->path); + if (!fh->dirs) + return EFI_DEVICE_ERROR; + } + + /* + * So this is a bit awkward. Since fs layer is stateful and we + * can't rewind an entry, in the EFI_BUFFER_TOO_SMALL case below + * we might have to return without consuming the dent.. so we + * have to stash it for next call. + */ + if (fh->dent) { + dent = fh->dent; + fh->dent = NULL; + } else { + dent = fs_readdir(fh->dirs); + } + + + if (!dent) { + /* no more files in directory: */ + /* workaround shim.efi bug/quirk.. as find_boot_csv() + * loops through directory contents, it initially calls + * read w/ zero length buffer to find out how much mem + * to allocate for the EFI_FILE_INFO, then allocates, + * and then calls a 2nd time. If we return size of + * zero the first time, it happily passes that to + * AllocateZeroPool(), and when that returns NULL it + * thinks it is EFI_OUT_OF_RESOURCES. So on first + * call return a non-zero size: + */ + if (*buffer_size == 0) + *buffer_size = sizeof(*info); + else + *buffer_size = 0; + return EFI_SUCCESS; + } + + /* check buffer size: */ + required_size = sizeof(*info) + 2 * (strlen(dent->name) + 1); + if (*buffer_size < required_size) { + *buffer_size = required_size; + fh->dent = dent; + return EFI_BUFFER_TOO_SMALL; + } + + *buffer_size = required_size; + memset(info, 0, required_size); + + info->size = required_size; + info->file_size = dent->size; + info->physical_size = dent->size; + + if (dent->type == FS_DT_DIR) + info->attribute |= EFI_FILE_DIRECTORY; + + ascii2unicode((u16 *)info->file_name, dent->name); + + fh->offset++; + + return EFI_SUCCESS; +} + +static efi_status_t EFIAPI efi_file_read(struct efi_file_handle *file, + u64 *buffer_size, void *buffer) +{ + struct file_handle *fh = to_fh(file); + efi_status_t ret = EFI_SUCCESS; + + EFI_ENTRY("%p, %p, %p", file, buffer_size, buffer); + + if (set_blk_dev(fh)) { + ret = EFI_DEVICE_ERROR; + goto error; + } + + if (fh->isdir) + ret = dir_read(fh, buffer_size, buffer); + else + ret = file_read(fh, buffer_size, buffer); + +error: + return EFI_EXIT(ret); +} + +static efi_status_t EFIAPI efi_file_write(struct efi_file_handle *file, + u64 *buffer_size, void *buffer) +{ + struct file_handle *fh = to_fh(file); + efi_status_t ret = EFI_SUCCESS; + loff_t actwrite; + + EFI_ENTRY("%p, %p, %p", file, buffer_size, buffer); + + if (set_blk_dev(fh)) { + ret = EFI_DEVICE_ERROR; + goto error; + } + + if (fs_write(fh->path, (ulong)buffer, fh->offset, *buffer_size, + &actwrite)) { + ret = EFI_DEVICE_ERROR; + goto error; + } + + *buffer_size = actwrite; + fh->offset += actwrite; + +error: + return EFI_EXIT(ret); +} + +static efi_status_t EFIAPI efi_file_getpos(struct efi_file_handle *file, + u64 *pos) +{ + struct file_handle *fh = to_fh(file); + EFI_ENTRY("%p, %p", file, pos); + *pos = fh->offset; + return EFI_EXIT(EFI_SUCCESS); +} + +static efi_status_t EFIAPI efi_file_setpos(struct efi_file_handle *file, + u64 pos) +{ + struct file_handle *fh = to_fh(file); + efi_status_t ret = EFI_SUCCESS; + + EFI_ENTRY("%p, %llu", file, pos); + + if (fh->isdir) { + if (pos != 0) { + ret = EFI_UNSUPPORTED; + goto error; + } + fs_closedir(fh->dirs); + fh->dirs = NULL; + } + + if (pos == ~0ULL) { + loff_t file_size; + + if (set_blk_dev(fh)) { + ret = EFI_DEVICE_ERROR; + goto error; + } + + if (fs_size(fh->path, &file_size)) { + ret = EFI_DEVICE_ERROR; + goto error; + } + + pos = file_size; + } + + fh->offset = pos; + +error: + return EFI_EXIT(ret); +} + +static efi_status_t EFIAPI efi_file_getinfo(struct efi_file_handle *file, + efi_guid_t *info_type, u64 *buffer_size, void *buffer) +{ + struct file_handle *fh = to_fh(file); + efi_status_t ret = EFI_SUCCESS; + + EFI_ENTRY("%p, %p, %p, %p", file, info_type, buffer_size, buffer); + + if (!guidcmp(info_type, &efi_file_info_guid)) { + struct efi_file_info *info = buffer; + char *filename = basename(fh); + unsigned int required_size; + loff_t file_size; + + /* check buffer size: */ + required_size = sizeof(*info) + 2 * (strlen(filename) + 1); + if (*buffer_size < required_size) { + *buffer_size = required_size; + ret = EFI_BUFFER_TOO_SMALL; + goto error; + } + + if (set_blk_dev(fh)) { + ret = EFI_DEVICE_ERROR; + goto error; + } + + if (fs_size(fh->path, &file_size)) { + ret = EFI_DEVICE_ERROR; + goto error; + } + + memset(info, 0, required_size); + + info->size = required_size; + info->file_size = file_size; + info->physical_size = file_size; + + if (fh->isdir) + info->attribute |= EFI_FILE_DIRECTORY; + + ascii2unicode((u16 *)info->file_name, filename); + } else { + ret = EFI_UNSUPPORTED; + } + +error: + return EFI_EXIT(ret); +} + +static efi_status_t EFIAPI efi_file_setinfo(struct efi_file_handle *file, + efi_guid_t *info_type, u64 buffer_size, void *buffer) +{ + EFI_ENTRY("%p, %p, %llu, %p", file, info_type, buffer_size, buffer); + return EFI_EXIT(EFI_UNSUPPORTED); +} + +static efi_status_t EFIAPI efi_file_flush(struct efi_file_handle *file) +{ + EFI_ENTRY("%p", file); + return EFI_EXIT(EFI_SUCCESS); +} + +static const struct efi_file_handle efi_file_handle_protocol = { + .rev = EFI_FILE_PROTOCOL_REVISION, + .open = efi_file_open, + .close = efi_file_close, + .delete = efi_file_delete, + .read = efi_file_read, + .write = efi_file_write, + .getpos = efi_file_getpos, + .setpos = efi_file_setpos, + .getinfo = efi_file_getinfo, + .setinfo = efi_file_setinfo, + .flush = efi_file_flush, +}; + +struct efi_file_handle *efi_file_from_path(struct efi_device_path *fp) +{ + struct efi_simple_file_system_protocol *v; + struct efi_file_handle *f; + efi_status_t ret; + + v = efi_fs_from_path(fp); + if (!v) + return NULL; + + EFI_CALL(ret = v->open_volume(v, &f)); + if (ret != EFI_SUCCESS) + return NULL; + + /* skip over device-path nodes before the file path: */ + while (fp && !EFI_DP_TYPE(fp, MEDIA_DEVICE, FILE_PATH)) + fp = efi_dp_next(fp); + + while (fp) { + struct efi_device_path_file_path *fdp = + container_of(fp, struct efi_device_path_file_path, dp); + struct efi_file_handle *f2; + + if (!EFI_DP_TYPE(fp, MEDIA_DEVICE, FILE_PATH)) { + printf("bad file path!\n"); + f->close(f); + return NULL; + } + + EFI_CALL(ret = f->open(f, &f2, (s16 *)fdp->str, + EFI_FILE_MODE_READ, 0)); + if (ret != EFI_SUCCESS) + return NULL; + + fp = efi_dp_next(fp); + + EFI_CALL(f->close(f)); + f = f2; + } + + return f; +} + +static efi_status_t EFIAPI +efi_open_volume(struct efi_simple_file_system_protocol *this, + struct efi_file_handle **root) +{ + struct file_system *fs = to_fs(this); + + EFI_ENTRY("%p, %p", this, root); + + *root = file_open(fs, NULL, NULL, 0); + + return EFI_EXIT(EFI_SUCCESS); +} + +struct efi_simple_file_system_protocol * +efi_simple_file_system(struct blk_desc *desc, int part, + struct efi_device_path *dp) +{ + struct file_system *fs; + + fs = calloc(1, sizeof(*fs)); + fs->base.rev = EFI_SIMPLE_FILE_SYSTEM_PROTOCOL_REVISION; + fs->base.open_volume = efi_open_volume; + fs->desc = desc; + fs->part = part; + fs->dp = dp; + + return &fs->base; +} diff --git a/lib/efi_loader/efi_image_loader.c b/lib/efi_loader/efi_image_loader.c index f961407f50..af29cc4f04 100644 --- a/lib/efi_loader/efi_image_loader.c +++ b/lib/efi_loader/efi_image_loader.c @@ -15,8 +15,12 @@ DECLARE_GLOBAL_DATA_PTR; +const efi_guid_t efi_global_variable_guid = EFI_GLOBAL_VARIABLE_GUID; const efi_guid_t efi_guid_device_path = DEVICE_PATH_GUID; const efi_guid_t efi_guid_loaded_image = LOADED_IMAGE_GUID; +const efi_guid_t efi_simple_file_system_protocol_guid = + EFI_SIMPLE_FILE_SYSTEM_PROTOCOL_GUID; +const efi_guid_t efi_file_info_guid = EFI_FILE_INFO_GUID; static efi_status_t efi_loader_relocate(const IMAGE_BASE_RELOCATION *rel, unsigned long rel_size, void *efi_reloc) @@ -90,6 +94,7 @@ void *efi_load_pe(void *efi, struct efi_loaded_image *loaded_image_info) unsigned long virt_size = 0; bool can_run_nt64 = true; bool can_run_nt32 = true; + uint16_t image_type; #if defined(CONFIG_ARM64) can_run_nt32 = false; @@ -135,6 +140,7 @@ void *efi_load_pe(void *efi, struct efi_loaded_image *loaded_image_info) entry = efi_reloc + opt->AddressOfEntryPoint; rel_size = opt->DataDirectory[rel_idx].Size; rel = efi_reloc + opt->DataDirectory[rel_idx].VirtualAddress; + image_type = opt->Subsystem; } else if (can_run_nt32 && (nt->OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR32_MAGIC)) { IMAGE_OPTIONAL_HEADER32 *opt = &nt->OptionalHeader; @@ -148,12 +154,32 @@ void *efi_load_pe(void *efi, struct efi_loaded_image *loaded_image_info) entry = efi_reloc + opt->AddressOfEntryPoint; rel_size = opt->DataDirectory[rel_idx].Size; rel = efi_reloc + opt->DataDirectory[rel_idx].VirtualAddress; + image_type = opt->Subsystem; } else { printf("%s: Invalid optional header magic %x\n", __func__, nt->OptionalHeader.Magic); return NULL; } + switch (image_type) { + case IMAGE_SUBSYSTEM_EFI_APPLICATION: + loaded_image_info->image_code_type = EFI_LOADER_CODE; + loaded_image_info->image_data_type = EFI_LOADER_DATA; + break; + case IMAGE_SUBSYSTEM_EFI_BOOT_SERVICE_DRIVER: + loaded_image_info->image_code_type = EFI_BOOT_SERVICES_CODE; + loaded_image_info->image_data_type = EFI_BOOT_SERVICES_DATA; + break; + case IMAGE_SUBSYSTEM_EFI_RUNTIME_DRIVER: + case IMAGE_SUBSYSTEM_SAL_RUNTIME_DRIVER: + loaded_image_info->image_code_type = EFI_RUNTIME_SERVICES_CODE; + loaded_image_info->image_data_type = EFI_RUNTIME_SERVICES_DATA; + break; + default: + printf("%s: invalid image type: %u\n", __func__, image_type); + break; + } + /* Load sections into RAM */ for (i = num_sections - 1; i >= 0; i--) { IMAGE_SECTION_HEADER *sec = §ions[i]; diff --git a/lib/efi_loader/efi_memory.c b/lib/efi_loader/efi_memory.c index 9e079f1fa3..d47759e08e 100644 --- a/lib/efi_loader/efi_memory.c +++ b/lib/efi_loader/efi_memory.c @@ -43,7 +43,7 @@ void *efi_bounce_buffer; */ struct efi_pool_allocation { u64 num_pages; - char data[]; + char data[] __aligned(ARCH_DMA_MINALIGN); }; /* @@ -356,7 +356,8 @@ efi_status_t efi_allocate_pool(int pool_type, unsigned long size, { efi_status_t r; efi_physical_addr_t t; - u64 num_pages = (size + sizeof(u64) + EFI_PAGE_MASK) >> EFI_PAGE_SHIFT; + u64 num_pages = (size + sizeof(struct efi_pool_allocation) + + EFI_PAGE_MASK) >> EFI_PAGE_SHIFT; if (size == 0) { *buffer = NULL; diff --git a/lib/efi_loader/efi_net.c b/lib/efi_loader/efi_net.c index 0b949d86e8..91f1e4a69e 100644 --- a/lib/efi_loader/efi_net.c +++ b/lib/efi_loader/efi_net.c @@ -26,9 +26,6 @@ struct efi_net_obj { /* EFI Interface callback struct for network */ struct efi_simple_network net; struct efi_simple_network_mode net_mode; - /* Device path to the network adapter */ - struct efi_device_path_mac_addr dp_mac; - struct efi_device_path_file_path dp_end; /* PXE struct to transmit dhcp data */ struct efi_pxe pxe; struct efi_pxe_mode pxe_mode; @@ -210,19 +207,9 @@ void efi_net_set_dhcp_ack(void *pkt, int len) } /* This gets called from do_bootefi_exec(). */ -int efi_net_register(void **handle) +int efi_net_register(void) { struct efi_net_obj *netobj; - struct efi_device_path_mac_addr dp_net = { - .dp.type = DEVICE_PATH_TYPE_MESSAGING_DEVICE, - .dp.sub_type = DEVICE_PATH_SUB_TYPE_MSG_MAC_ADDR, - .dp.length = sizeof(dp_net), - }; - struct efi_device_path_file_path dp_end = { - .dp.type = DEVICE_PATH_TYPE_END, - .dp.sub_type = DEVICE_PATH_SUB_TYPE_END, - .dp.length = sizeof(dp_end), - }; if (!eth_get_dev()) { /* No eth device active, don't expose any */ @@ -236,7 +223,8 @@ int efi_net_register(void **handle) netobj->parent.protocols[0].guid = &efi_net_guid; netobj->parent.protocols[0].protocol_interface = &netobj->net; netobj->parent.protocols[1].guid = &efi_guid_device_path; - netobj->parent.protocols[1].protocol_interface = &netobj->dp_mac; + netobj->parent.protocols[1].protocol_interface = + efi_dp_from_eth(); netobj->parent.protocols[2].guid = &efi_pxe_guid; netobj->parent.protocols[2].protocol_interface = &netobj->pxe; netobj->parent.handle = &netobj->net; @@ -255,9 +243,6 @@ int efi_net_register(void **handle) netobj->net.receive = efi_net_receive; netobj->net.mode = &netobj->net_mode; netobj->net_mode.state = EFI_NETWORK_STARTED; - netobj->dp_mac = dp_net; - netobj->dp_end = dp_end; - memcpy(netobj->dp_mac.mac.addr, eth_get_ethaddr(), 6); memcpy(netobj->net_mode.current_address.mac_addr, eth_get_ethaddr(), 6); netobj->net_mode.max_packet_size = PKTSIZE; @@ -268,8 +253,5 @@ int efi_net_register(void **handle) /* Hook net up to the device list */ list_add_tail(&netobj->parent.link, &efi_obj_list); - if (handle) - *handle = &netobj->net; - return 0; } diff --git a/lib/efi_loader/efi_runtime.c b/lib/efi_loader/efi_runtime.c index ad7f3754bd..8104e08c46 100644 --- a/lib/efi_loader/efi_runtime.c +++ b/lib/efi_loader/efi_runtime.c @@ -184,7 +184,16 @@ static const struct efi_runtime_detach_list_struct efi_runtime_detach_list[] = { /* Clean up system table */ .ptr = &systab.boottime, .patchto = NULL, - }, + }, { + .ptr = &efi_runtime_services.get_variable, + .patchto = &efi_device_error, + }, { + .ptr = &efi_runtime_services.get_next_variable, + .patchto = &efi_device_error, + }, { + .ptr = &efi_runtime_services.set_variable, + .patchto = &efi_device_error, + } }; static bool efi_runtime_tobedetached(void *p) @@ -243,7 +252,8 @@ void efi_runtime_relocate(ulong offset, struct efi_mem_desc *map) /* Check if the relocation is inside bounds */ if (map && ((newaddr < map->virtual_start) || - newaddr > (map->virtual_start + (map->num_pages << 12)))) { + newaddr > (map->virtual_start + + (map->num_pages << EFI_PAGE_SHIFT)))) { if (!efi_runtime_tobedetached(p)) printf("U-Boot EFI: Relocation at %p is out of " "range (%lx)\n", p, newaddr); @@ -269,7 +279,8 @@ static efi_status_t EFIAPI efi_set_virtual_address_map( uint32_t descriptor_version, struct efi_mem_desc *virtmap) { - ulong runtime_start = (ulong)&__efi_runtime_start & ~0xfffULL; + ulong runtime_start = (ulong)&__efi_runtime_start & + ~(ulong)EFI_PAGE_MASK; int n = memory_map_size / descriptor_size; int i; @@ -382,9 +393,9 @@ struct efi_runtime_services __efi_runtime_data efi_runtime_services = { .set_wakeup_time = (void *)&efi_unimplemented, .set_virtual_address_map = &efi_set_virtual_address_map, .convert_pointer = (void *)&efi_invalid_parameter, - .get_variable = (void *)&efi_device_error, - .get_next_variable = (void *)&efi_device_error, - .set_variable = (void *)&efi_device_error, + .get_variable = efi_get_variable, + .get_next_variable = efi_get_next_variable, + .set_variable = efi_set_variable, .get_next_high_mono_count = (void *)&efi_device_error, .reset_system = &efi_reset_system_boottime, }; diff --git a/lib/efi_loader/efi_variable.c b/lib/efi_loader/efi_variable.c new file mode 100644 index 0000000000..6c177da3a6 --- /dev/null +++ b/lib/efi_loader/efi_variable.c @@ -0,0 +1,335 @@ +/* + * EFI utils + * + * Copyright (c) 2017 Rob Clark + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +#include <malloc.h> +#include <charset.h> +#include <efi_loader.h> + +#define READ_ONLY BIT(31) + +/* + * Mapping between EFI variables and u-boot variables: + * + * efi_$guid_$varname = {attributes}(type)value + * + * For example: + * + * efi_8be4df61-93ca-11d2-aa0d-00e098032b8c_OsIndicationsSupported= + * "{ro,boot,run}(blob)0000000000000000" + * efi_8be4df61-93ca-11d2-aa0d-00e098032b8c_BootOrder= + * "(blob)00010000" + * + * The attributes are a comma separated list of these possible + * attributes: + * + * + ro - read-only + * + boot - boot-services access + * + run - runtime access + * + * NOTE: with current implementation, no variables are available after + * ExitBootServices, and all are persisted (if possible). + * + * If not specified, the attributes default to "{boot}". + * + * The required type is one of: + * + * + utf8 - raw utf8 string + * + blob - arbitrary length hex string + * + * Maybe a utf16 type would be useful to for a string value to be auto + * converted to utf16? + */ + +#define MAX_VAR_NAME 31 +#define MAX_NATIVE_VAR_NAME \ + (strlen("efi_xxxxxxxx-xxxx-xxxx-xxxxxxxxxxxxxxxx_") + \ + (MAX_VAR_NAME * MAX_UTF8_PER_UTF16)) + +static int hex(unsigned char ch) +{ + if (ch >= 'a' && ch <= 'f') + return ch-'a'+10; + if (ch >= '0' && ch <= '9') + return ch-'0'; + if (ch >= 'A' && ch <= 'F') + return ch-'A'+10; + return -1; +} + +static const char *hex2mem(u8 *mem, const char *hexstr, int count) +{ + memset(mem, 0, count/2); + + do { + int nibble; + + *mem = 0; + + if (!count || !*hexstr) + break; + + nibble = hex(*hexstr); + if (nibble < 0) + break; + + *mem = nibble; + count--; + hexstr++; + + if (!count || !*hexstr) + break; + + nibble = hex(*hexstr); + if (nibble < 0) + break; + + *mem = (*mem << 4) | nibble; + count--; + hexstr++; + mem++; + + } while (1); + + if (*hexstr) + return hexstr; + + return NULL; +} + +static char *mem2hex(char *hexstr, const u8 *mem, int count) +{ + static const char hexchars[] = "0123456789abcdef"; + + while (count-- > 0) { + u8 ch = *mem++; + *hexstr++ = hexchars[ch >> 4]; + *hexstr++ = hexchars[ch & 0xf]; + } + + return hexstr; +} + +static efi_status_t efi_to_native(char *native, s16 *variable_name, + efi_guid_t *vendor) +{ + size_t len; + + len = utf16_strlen((u16 *)variable_name); + if (len >= MAX_VAR_NAME) + return EFI_DEVICE_ERROR; + + native += sprintf(native, "efi_%pUl_", vendor); + native = (char *)utf16_to_utf8((u8 *)native, (u16 *)variable_name, len); + *native = '\0'; + + return EFI_SUCCESS; +} + +static const char *prefix(const char *str, const char *prefix) +{ + size_t n = strlen(prefix); + if (!strncmp(prefix, str, n)) + return str + n; + return NULL; +} + +/* parse attributes part of variable value, if present: */ +static const char *parse_attr(const char *str, u32 *attrp) +{ + u32 attr = 0; + char sep = '{'; + + if (*str != '{') { + *attrp = EFI_VARIABLE_BOOTSERVICE_ACCESS; + return str; + } + + while (*str == sep) { + const char *s; + + str++; + + if ((s = prefix(str, "ro"))) { + attr |= READ_ONLY; + } else if ((s = prefix(str, "boot"))) { + attr |= EFI_VARIABLE_BOOTSERVICE_ACCESS; + } else if ((s = prefix(str, "run"))) { + attr |= EFI_VARIABLE_RUNTIME_ACCESS; + } else { + printf("invalid attribute: %s\n", str); + break; + } + + str = s; + sep = ','; + } + + str++; + + *attrp = attr; + + return str; +} + +/* http://wiki.phoenix.com/wiki/index.php/EFI_RUNTIME_SERVICES#GetVariable.28.29 */ +efi_status_t EFIAPI efi_get_variable(s16 *variable_name, + efi_guid_t *vendor, u32 *attributes, + unsigned long *data_size, void *data) +{ + char native_name[MAX_NATIVE_VAR_NAME + 1]; + efi_status_t ret; + unsigned long in_size; + const char *val, *s; + u32 attr; + + EFI_ENTRY("\"%ls\" %pUl %p %p %p", variable_name, vendor, attributes, + data_size, data); + + if (!variable_name || !vendor || !data_size) + return EFI_EXIT(EFI_INVALID_PARAMETER); + + ret = efi_to_native(native_name, variable_name, vendor); + if (ret) + return EFI_EXIT(ret); + + debug("%s: get '%s'\n", __func__, native_name); + + val = env_get(native_name); + if (!val) + return EFI_EXIT(EFI_NOT_FOUND); + + val = parse_attr(val, &attr); + + in_size = *data_size; + + if ((s = prefix(val, "(blob)"))) { + unsigned len = strlen(s); + + /* two characters per byte: */ + len = DIV_ROUND_UP(len, 2); + *data_size = len; + + if (in_size < len) + return EFI_EXIT(EFI_BUFFER_TOO_SMALL); + + if (!data) + return EFI_EXIT(EFI_INVALID_PARAMETER); + + if (hex2mem(data, s, len * 2)) + return EFI_EXIT(EFI_DEVICE_ERROR); + + debug("%s: got value: \"%s\"\n", __func__, s); + } else if ((s = prefix(val, "(utf8)"))) { + unsigned len = strlen(s) + 1; + + *data_size = len; + + if (in_size < len) + return EFI_EXIT(EFI_BUFFER_TOO_SMALL); + + if (!data) + return EFI_EXIT(EFI_INVALID_PARAMETER); + + memcpy(data, s, len); + ((char *)data)[len] = '\0'; + + debug("%s: got value: \"%s\"\n", __func__, (char *)data); + } else { + debug("%s: invalid value: '%s'\n", __func__, val); + return EFI_EXIT(EFI_DEVICE_ERROR); + } + + if (attributes) + *attributes = attr & EFI_VARIABLE_MASK; + + return EFI_EXIT(EFI_SUCCESS); +} + +/* http://wiki.phoenix.com/wiki/index.php/EFI_RUNTIME_SERVICES#GetNextVariableName.28.29 */ +efi_status_t EFIAPI efi_get_next_variable( + unsigned long *variable_name_size, + s16 *variable_name, efi_guid_t *vendor) +{ + EFI_ENTRY("%p \"%ls\" %pUl", variable_name_size, variable_name, vendor); + + return EFI_EXIT(EFI_DEVICE_ERROR); +} + +/* http://wiki.phoenix.com/wiki/index.php/EFI_RUNTIME_SERVICES#SetVariable.28.29 */ +efi_status_t EFIAPI efi_set_variable(s16 *variable_name, + efi_guid_t *vendor, u32 attributes, + unsigned long data_size, void *data) +{ + char native_name[MAX_NATIVE_VAR_NAME + 1]; + efi_status_t ret = EFI_SUCCESS; + char *val, *s; + u32 attr; + + EFI_ENTRY("\"%ls\" %pUl %x %lu %p", variable_name, vendor, attributes, + data_size, data); + + if (!variable_name || !vendor) + return EFI_EXIT(EFI_INVALID_PARAMETER); + + ret = efi_to_native(native_name, variable_name, vendor); + if (ret) + return EFI_EXIT(ret); + +#define ACCESS_ATTR (EFI_VARIABLE_RUNTIME_ACCESS | EFI_VARIABLE_BOOTSERVICE_ACCESS) + + if ((data_size == 0) || !(attributes & ACCESS_ATTR)) { + /* delete the variable: */ + env_set(native_name, NULL); + return EFI_EXIT(EFI_SUCCESS); + } + + val = env_get(native_name); + if (val) { + parse_attr(val, &attr); + + if (attr & READ_ONLY) + return EFI_EXIT(EFI_WRITE_PROTECTED); + } + + val = malloc(2 * data_size + strlen("{ro,run,boot}(blob)") + 1); + if (!val) + return EFI_EXIT(EFI_OUT_OF_RESOURCES); + + s = val; + + /* store attributes: */ + attributes &= (EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_RUNTIME_ACCESS); + s += sprintf(s, "{"); + while (attributes) { + u32 attr = 1 << (ffs(attributes) - 1); + + if (attr == EFI_VARIABLE_BOOTSERVICE_ACCESS) + s += sprintf(s, "boot"); + else if (attr == EFI_VARIABLE_RUNTIME_ACCESS) + s += sprintf(s, "run"); + + attributes &= ~attr; + if (attributes) + s += sprintf(s, ","); + } + s += sprintf(s, "}"); + + /* store payload: */ + s += sprintf(s, "(blob)"); + s = mem2hex(s, data, data_size); + *s = '\0'; + + debug("%s: setting: %s=%s\n", __func__, native_name, val); + + if (env_set(native_name, val)) + ret = EFI_DEVICE_ERROR; + + free(val); + + return EFI_EXIT(ret); +} |