// SPDX-License-Identifier: GPL-2.0+
/*
- * EFI application boot time services
+ * EFI application boot time services
*
- * Copyright (c) 2016 Alexander Graf
+ * Copyright (c) 2016 Alexander Graf
*/
#include <common.h>
#include <div64.h>
#include <efi_loader.h>
-#include <environment.h>
#include <malloc.h>
#include <linux/libfdt_env.h>
#include <u-boot/crc.h>
LIST_HEAD(efi_obj_list);
/* List of all events */
-LIST_HEAD(efi_events);
+__efi_runtime_data LIST_HEAD(efi_events);
+
+/* List of queued events */
+LIST_HEAD(efi_event_queue);
+
+/* Flag to disable timer activity in ExitBootServices() */
+static bool timers_enabled = true;
/* List of all events registered by RegisterProtocolNotify() */
LIST_HEAD(efi_register_notify_events);
/* Handle of the currently executing image */
static efi_handle_t current_image;
-/*
- * If we're running on nasty systems (32bit ARM booting into non-EFI Linux)
- * we need to do trickery with caches. Since we don't want to break the EFI
- * aware boot path, only apply hacks when loading exiting directly (breaking
- * direct Linux EFI booting along the way - oh well).
- */
-static bool efi_is_direct_boot = true;
-
#ifdef CONFIG_ARM
/*
* The "gd" pointer lives in a register on ARM and AArch64 that we declare
return indent_string(--nesting_level);
}
+/**
+ * efi_event_is_queued() - check if an event is queued
+ *
+ * @event: event
+ * Return: true if event is queued
+ */
+static bool efi_event_is_queued(struct efi_event *event)
+{
+ return !!event->queue_link.next;
+}
+
+/**
+ * efi_process_event_queue() - process event queue
+ */
+static void efi_process_event_queue(void)
+{
+ while (!list_empty(&efi_event_queue)) {
+ struct efi_event *event;
+ efi_uintn_t old_tpl;
+
+ event = list_first_entry(&efi_event_queue, struct efi_event,
+ queue_link);
+ if (efi_tpl >= event->notify_tpl)
+ return;
+ list_del(&event->queue_link);
+ event->queue_link.next = NULL;
+ event->queue_link.prev = NULL;
+ /* Events must be executed at the event's TPL */
+ old_tpl = efi_tpl;
+ efi_tpl = event->notify_tpl;
+ EFI_CALL_VOID(event->notify_function(event,
+ event->notify_context));
+ efi_tpl = old_tpl;
+ if (event->type == EVT_NOTIFY_SIGNAL)
+ event->is_signaled = 0;
+ }
+}
+
/**
* efi_queue_event() - queue an EFI event
* @event: event to signal
- * @check_tpl: check the TPL level
*
* This function queues the notification function of the event for future
* execution.
*
- * The notification function is called if the task priority level of the event
- * is higher than the current task priority level.
- *
- * For the SignalEvent service see efi_signal_event_ext.
- *
*/
-static void efi_queue_event(struct efi_event *event, bool check_tpl)
+static void efi_queue_event(struct efi_event *event)
{
- if (event->notify_function) {
- event->is_queued = true;
- /* Check TPL */
- if (check_tpl && efi_tpl >= event->notify_tpl)
- return;
- event->is_queued = false;
- EFI_CALL_VOID(event->notify_function(event,
- event->notify_context));
- } else {
- event->is_queued = false;
+ struct efi_event *item = NULL;
+
+ if (!event->notify_function)
+ return;
+
+ if (!efi_event_is_queued(event)) {
+ /*
+ * Events must be notified in order of decreasing task priority
+ * level. Insert the new event accordingly.
+ */
+ list_for_each_entry(item, &efi_event_queue, queue_link) {
+ if (item->notify_tpl < event->notify_tpl) {
+ list_add_tail(&event->queue_link,
+ &item->queue_link);
+ event = NULL;
+ break;
+ }
+ }
+ if (event)
+ list_add_tail(&event->queue_link, &efi_event_queue);
}
+ efi_process_event_queue();
}
/**
/**
* efi_signal_event() - signal an EFI event
* @event: event to signal
- * @check_tpl: check the TPL level
*
* This function signals an event. If the event belongs to an event group all
* events of the group are signaled. If they are of type EVT_NOTIFY_SIGNAL
*
* For the SignalEvent service see efi_signal_event_ext.
*/
-void efi_signal_event(struct efi_event *event, bool check_tpl)
+void efi_signal_event(struct efi_event *event)
{
+ if (event->is_signaled)
+ return;
if (event->group) {
struct efi_event *evt;
if (evt->is_signaled)
continue;
evt->is_signaled = true;
- if (evt->type & EVT_NOTIFY_SIGNAL &&
- evt->notify_function)
- evt->is_queued = true;
}
list_for_each_entry(evt, &efi_events, link) {
if (!evt->group || guidcmp(evt->group, event->group))
continue;
- if (evt->is_queued)
- efi_queue_event(evt, check_tpl);
+ efi_queue_event(evt);
}
} else {
event->is_signaled = true;
- if (event->type & EVT_NOTIFY_SIGNAL)
- efi_queue_event(event, check_tpl);
+ efi_queue_event(event);
}
}
/**
* efi_delete_handle() - delete handle
*
- * @obj: handle to delete
+ * @handle: handle to delete
*/
void efi_delete_handle(efi_handle_t handle)
{
/**
* efi_create_event() - create an event
+ *
* @type: type of the event to create
* @notify_tpl: task priority level of the event
* @notify_function: notification function of the event
struct efi_event **event)
{
struct efi_event *evt;
+ efi_status_t ret;
+ int pool_type;
if (event == NULL)
return EFI_INVALID_PARAMETER;
case EVT_NOTIFY_WAIT:
case EVT_TIMER | EVT_NOTIFY_WAIT:
case EVT_SIGNAL_EXIT_BOOT_SERVICES:
+ pool_type = EFI_BOOT_SERVICES_DATA;
+ break;
case EVT_SIGNAL_VIRTUAL_ADDRESS_CHANGE:
+ pool_type = EFI_RUNTIME_SERVICES_DATA;
break;
default:
return EFI_INVALID_PARAMETER;
(!notify_function || is_valid_tpl(notify_tpl) != EFI_SUCCESS))
return EFI_INVALID_PARAMETER;
- evt = calloc(1, sizeof(struct efi_event));
- if (!evt)
- return EFI_OUT_OF_RESOURCES;
+ ret = efi_allocate_pool(pool_type, sizeof(struct efi_event),
+ (void **)&evt);
+ if (ret != EFI_SUCCESS)
+ return ret;
+ memset(evt, 0, sizeof(struct efi_event));
evt->type = type;
evt->notify_tpl = notify_tpl;
evt->notify_function = notify_function;
evt->group = group;
/* Disable timers on boot up */
evt->trigger_next = -1ULL;
- evt->is_queued = false;
- evt->is_signaled = false;
list_add_tail(&evt->link, &efi_events);
*event = evt;
return EFI_SUCCESS;
u64 now = timer_get_us();
list_for_each_entry(evt, &efi_events, link) {
- if (evt->is_queued)
- efi_queue_event(evt, true);
+ if (!timers_enabled)
+ continue;
if (!(evt->type & EVT_TIMER) || now < evt->trigger_next)
continue;
switch (evt->trigger_type) {
continue;
}
evt->is_signaled = false;
- efi_signal_event(evt, true);
+ efi_signal_event(evt);
}
+ efi_process_event_queue();
WATCHDOG_RESET();
}
if (!event[i]->type || event[i]->type & EVT_NOTIFY_SIGNAL)
return EFI_EXIT(EFI_INVALID_PARAMETER);
if (!event[i]->is_signaled)
- efi_queue_event(event[i], true);
+ efi_queue_event(event[i]);
}
/* Wait for signal */
EFI_ENTRY("%p", event);
if (efi_is_event(event) != EFI_SUCCESS)
return EFI_EXIT(EFI_INVALID_PARAMETER);
- efi_signal_event(event, true);
+ efi_signal_event(event);
return EFI_EXIT(EFI_SUCCESS);
}
free(item);
}
}
+ /* Remove event from queue */
+ if (efi_event_is_queued(event))
+ list_del(&event->queue_link);
list_del(&event->link);
- free(event);
+ efi_free_pool(event);
return EFI_EXIT(EFI_SUCCESS);
}
event->type & EVT_NOTIFY_SIGNAL)
return EFI_EXIT(EFI_INVALID_PARAMETER);
if (!event->is_signaled)
- efi_queue_event(event, true);
+ efi_queue_event(event);
if (event->is_signaled) {
event->is_signaled = false;
return EFI_EXIT(EFI_SUCCESS);
}
notif->handle = handle;
list_add_tail(¬if->link, &event->handles);
- efi_signal_event(event->event, true);
+ event->event->is_signaled = false;
+ efi_signal_event(event->event);
}
}
++count;
}
}
+ *number_of_drivers = 0;
+ if (!count) {
+ *driver_handle_buffer = NULL;
+ return EFI_SUCCESS;
+ }
/*
* Create buffer. In case of duplicate driver assignments the buffer
* will be too large. But that does not harm.
*/
- *number_of_drivers = 0;
*driver_handle_buffer = calloc(count, sizeof(efi_handle_t));
if (!*driver_handle_buffer)
return EFI_OUT_OF_RESOURCES;
&driver_handle_buffer);
if (ret != EFI_SUCCESS)
return ret;
-
+ if (!number_of_drivers)
+ return EFI_SUCCESS;
ret = EFI_NOT_FOUND;
while (number_of_drivers) {
r = EFI_CALL(efi_disconnect_controller(
goto out;
/* Disconnect controllers */
efi_disconnect_all_drivers(efiobj, protocol, NULL);
- if (!list_empty(&handler->open_infos)) {
- r = EFI_ACCESS_DENIED;
- goto out;
- }
/* Close protocol */
list_for_each_entry_safe(item, pos, &handler->open_infos, link) {
if (item->info.attributes ==
/**
* efi_search() - determine if an EFI handle implements a protocol
+ *
* @search_type: selection criterion
* @protocol: GUID of the protocol
- * @search_key: registration key
* @handle: handle
*
* See the documentation of the LocateHandle service in the UEFI specification.
/* Notify that the configuration table was changed */
list_for_each_entry(evt, &efi_events, link) {
if (evt->group && !guidcmp(evt->group, guid)) {
- efi_signal_event(evt, false);
+ efi_signal_event(evt);
break;
}
}
* Initialize a loaded_image_info and loaded_image_info object with correct
* protocols, boot-device, etc.
*
- * In case of an error *handle_ptr and *info_ptr are set to NULL and an error
+ * In case of an error \*handle_ptr and \*info_ptr are set to NULL and an error
* code is returned.
*
* @device_path: device path of the loaded image
/* Open file */
f = efi_file_from_path(file_path);
if (!f)
- return EFI_DEVICE_ERROR;
+ return EFI_NOT_FOUND;
/* Get file size */
bs = 0;
EFI_ENTRY("%d, %p, %pD, %p, %zd, %p", boot_policy, parent_image,
file_path, source_buffer, source_size, image_handle);
- if (!image_handle || !efi_search_obj(parent_image)) {
- ret = EFI_INVALID_PARAMETER;
- goto error;
- }
-
- if (!source_buffer && !file_path) {
- ret = EFI_NOT_FOUND;
- goto error;
- }
- /* The parent image handle must refer to a loaded image */
- if (!parent_image->type) {
+ if (!image_handle || (!source_buffer && !file_path) ||
+ !efi_search_obj(parent_image) ||
+ /* The parent image handle must refer to a loaded image */
+ !parent_image->type) {
ret = EFI_INVALID_PARAMETER;
goto error;
}
*/
static void efi_exit_caches(void)
{
-#if defined(CONFIG_ARM) && !defined(CONFIG_ARM64)
+#if defined(CONFIG_EFI_GRUB_ARM32_WORKAROUND)
/*
- * Grub on 32bit ARM needs to have caches disabled before jumping into
- * a zImage, but does not know of all cache layers. Give it a hand.
+ * Boooting Linux via GRUB prior to version 2.04 fails on 32bit ARM if
+ * caches are enabled.
+ *
+ * TODO:
+ * According to the UEFI spec caches that can be managed via CP15
+ * operations should be enabled. Caches requiring platform information
+ * to manage should be disabled. This should not happen in
+ * ExitBootServices() but before invoking any UEFI binary is invoked.
+ *
+ * We want to keep the current workaround while GRUB prior to version
+ * 2.04 is still in use.
*/
- if (efi_is_direct_boot)
- cleanup_before_linux();
+ cleanup_before_linux();
#endif
}
static efi_status_t EFIAPI efi_exit_boot_services(efi_handle_t image_handle,
efi_uintn_t map_key)
{
- struct efi_event *evt;
+ struct efi_event *evt, *next_event;
+ efi_status_t ret = EFI_SUCCESS;
EFI_ENTRY("%p, %zx", image_handle, map_key);
/* Check that the caller has read the current memory map */
- if (map_key != efi_memory_map_key)
- return EFI_INVALID_PARAMETER;
-
- /* Make sure that notification functions are not called anymore */
- efi_tpl = TPL_HIGH_LEVEL;
+ if (map_key != efi_memory_map_key) {
+ ret = EFI_INVALID_PARAMETER;
+ goto out;
+ }
/* Check if ExitBootServices has already been called */
if (!systab.boottime)
- return EFI_EXIT(EFI_SUCCESS);
+ goto out;
+
+ /* Stop all timer related activities */
+ timers_enabled = false;
/* Add related events to the event group */
list_for_each_entry(evt, &efi_events, link) {
if (evt->group &&
!guidcmp(evt->group,
&efi_guid_event_group_exit_boot_services)) {
- efi_signal_event(evt, false);
+ efi_signal_event(evt);
break;
}
}
- /* TODO: Should persist EFI variables here */
+ /* Make sure that notification functions are not called anymore */
+ efi_tpl = TPL_HIGH_LEVEL;
+
+ /* Notify variable services */
+ efi_variables_boot_exit_notify();
+
+ /* Remove all events except EVT_SIGNAL_VIRTUAL_ADDRESS_CHANGE */
+ list_for_each_entry_safe(evt, next_event, &efi_events, link) {
+ if (evt->type != EVT_SIGNAL_VIRTUAL_ADDRESS_CHANGE)
+ list_del(&evt->link);
+ }
board_quiesce_devices();
+ /* Patch out unsupported runtime function */
+ efi_runtime_detach();
+
/* Fix up caches for EFI payloads if necessary */
efi_exit_caches();
/* Give the payload some time to boot */
efi_set_watchdog(0);
WATCHDOG_RESET();
-
- return EFI_EXIT(EFI_SUCCESS);
+out:
+ return EFI_EXIT(ret);
}
/**
*/
static efi_status_t EFIAPI efi_stall(unsigned long microseconds)
{
+ u64 end_tick;
+
EFI_ENTRY("%ld", microseconds);
- udelay(microseconds);
+
+ end_tick = get_ticks() + usec_to_tick(microseconds);
+ while (get_ticks() < end_tick)
+ efi_timer_check();
+
return EFI_EXIT(EFI_SUCCESS);
}
item->info.controller_handle == controller_handle) {
efi_delete_open_info(item);
r = EFI_SUCCESS;
- break;
}
}
out:
/* Find existing entry */
list_for_each_entry(item, &handler->open_infos, link) {
if (item->info.agent_handle == agent_handle &&
- item->info.controller_handle == controller_handle)
+ item->info.controller_handle == controller_handle &&
+ item->info.attributes == attributes)
match = &item->info;
}
/* None found, create one */
EFI_ENTRY("%p, %p, %p", image_handle, exit_data_size, exit_data);
/* Check parameters */
+ if (image_obj->header.type != EFI_OBJECT_TYPE_LOADED_IMAGE)
+ return EFI_EXIT(EFI_INVALID_PARAMETER);
+
ret = EFI_CALL(efi_open_protocol(image_handle, &efi_guid_loaded_image,
&info, NULL, NULL,
EFI_OPEN_PROTOCOL_GET_PROTOCOL));
if (ret != EFI_SUCCESS)
return EFI_EXIT(EFI_INVALID_PARAMETER);
- efi_is_direct_boot = false;
-
image_obj->exit_data_size = exit_data_size;
image_obj->exit_data = exit_data;
* @image_obj: handle of the loaded image
* @loaded_image_protocol: loaded image protocol
*/
-static void efi_delete_image(struct efi_loaded_image_obj *image_obj,
- struct efi_loaded_image *loaded_image_protocol)
+static efi_status_t efi_delete_image
+ (struct efi_loaded_image_obj *image_obj,
+ struct efi_loaded_image *loaded_image_protocol)
{
+ struct efi_object *efiobj;
+ efi_status_t r, ret = EFI_SUCCESS;
+
+close_next:
+ list_for_each_entry(efiobj, &efi_obj_list, link) {
+ struct efi_handler *protocol;
+
+ list_for_each_entry(protocol, &efiobj->protocols, link) {
+ struct efi_open_protocol_info_item *info;
+
+ list_for_each_entry(info, &protocol->open_infos, link) {
+ if (info->info.agent_handle !=
+ (efi_handle_t)image_obj)
+ continue;
+ r = EFI_CALL(efi_close_protocol
+ (efiobj, protocol->guid,
+ info->info.agent_handle,
+ info->info.controller_handle
+ ));
+ if (r != EFI_SUCCESS)
+ ret = r;
+ /*
+ * Closing protocols may results in further
+ * items being deleted. To play it safe loop
+ * over all elements again.
+ */
+ goto close_next;
+ }
+ }
+ }
+
efi_free_pages((uintptr_t)loaded_image_protocol->image_base,
efi_size_in_pages(loaded_image_protocol->image_size));
efi_delete_handle(&image_obj->header);
+
+ return ret;
}
/**
/**
* efi_update_exit_data() - fill exit data parameters of StartImage()
*
- * @image_obj image handle
- * @exit_data_size size of the exit data buffer
- * @exit_data buffer with data returned by UEFI payload
+ * @image_obj: image handle
+ * @exit_data_size: size of the exit data buffer
+ * @exit_data: buffer with data returned by UEFI payload
* Return: status code
*/
static efi_status_t efi_update_exit_data(struct efi_loaded_image_obj *image_obj,
const efi_guid_t *protocol,
void **protocol_interface)
{
- return efi_open_protocol(handle, protocol, protocol_interface, NULL,
+ return efi_open_protocol(handle, protocol, protocol_interface, efi_root,
NULL, EFI_OPEN_PROTOCOL_BY_HANDLE_PROTOCOL);
}
if (r != EFI_SUCCESS)
return r;
- /* Context Override */
+ /* Context Override */
if (driver_image_handle) {
for (; *driver_image_handle; ++driver_image_handle) {
for (i = 0; i < count; ++i) {
}
}
}
- /* Check for child controller specified by end node */
+ /* Check for child controller specified by end node */
if (ret != EFI_SUCCESS && remain_device_path &&
remain_device_path->type == DEVICE_PATH_TYPE_END)
ret = EFI_SUCCESS;
},
.fw_vendor = firmware_vendor,
.fw_revision = FW_VERSION << 16 | FW_PATCHLEVEL << 8,
- .con_in = (void *)&efi_con_in,
- .con_out = (void *)&efi_con_out,
- .std_err = (void *)&efi_con_out,
- .runtime = (void *)&efi_runtime_services,
- .boottime = (void *)&efi_boot_services,
+ .runtime = &efi_runtime_services,
.nr_tables = 0,
.tables = NULL,
};
sizeof(struct efi_configuration_table),
(void **)&systab.tables);
+ /*
+ * These entries will be set to NULL in ExitBootServices(). To avoid
+ * relocation in SetVirtualAddressMap(), set them dynamically.
+ */
+ systab.con_in = &efi_con_in;
+ systab.con_out = &efi_con_out;
+ systab.std_err = &efi_con_out;
+ systab.boottime = &efi_boot_services;
+
/* Set CRC32 field in table headers */
efi_update_table_header_crc32(&systab.hdr);
efi_update_table_header_crc32(&efi_runtime_services.hdr);