common: Move enable/disable_interrupts out of common.h
[oweals/u-boot.git] / lib / efi_loader / efi_boottime.c
index b583ac6a42641a67f316fb1764d38269cc00cc63..88a7604bbf33710da7ab7b255d181dc5ed97d957 100644 (file)
@@ -1,19 +1,21 @@
 // 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 <irq_func.h>
 #include <malloc.h>
+#include <time.h>
 #include <linux/libfdt_env.h>
 #include <u-boot/crc.h>
 #include <bootm.h>
 #include <pe.h>
+#include <u-boot/crc.h>
 #include <watchdog.h>
 
 DECLARE_GLOBAL_DATA_PTR;
@@ -25,7 +27,13 @@ static efi_uintn_t efi_tpl = TPL_APPLICATION;
 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);
@@ -33,14 +41,6 @@ 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
@@ -160,31 +160,76 @@ const char *__efi_nesting_dec(void)
        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;
-               EFI_CALL_VOID(event->notify_function(event,
-                                                    event->notify_context));
+       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);
        }
-       event->is_queued = false;
+       efi_process_event_queue();
 }
 
 /**
@@ -209,7 +254,6 @@ efi_status_t is_valid_tpl(efi_uintn_t tpl)
 /**
  * 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
@@ -217,8 +261,10 @@ efi_status_t is_valid_tpl(efi_uintn_t tpl)
  *
  * 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;
 
@@ -232,20 +278,15 @@ void efi_signal_event(struct efi_event *event, bool check_tpl)
                        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);
        }
 }
 
@@ -549,7 +590,7 @@ efi_status_t efi_remove_all_protocols(const efi_handle_t handle)
 /**
  * efi_delete_handle() - delete handle
  *
- * @obj: handle to delete
+ * @handle: handle to delete
  */
 void efi_delete_handle(efi_handle_t handle)
 {
@@ -581,6 +622,7 @@ static efi_status_t efi_is_event(const struct efi_event *event)
 
 /**
  * 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
@@ -603,6 +645,8 @@ efi_status_t efi_create_event(uint32_t type, efi_uintn_t notify_tpl,
                              struct efi_event **event)
 {
        struct efi_event *evt;
+       efi_status_t ret;
+       int pool_type;
 
        if (event == NULL)
                return EFI_INVALID_PARAMETER;
@@ -615,7 +659,10 @@ efi_status_t efi_create_event(uint32_t type, efi_uintn_t notify_tpl,
        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;
@@ -625,9 +672,11 @@ efi_status_t efi_create_event(uint32_t type, efi_uintn_t notify_tpl,
            (!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;
@@ -635,8 +684,6 @@ efi_status_t efi_create_event(uint32_t type, efi_uintn_t notify_tpl,
        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;
@@ -731,8 +778,8 @@ void efi_timer_check(void)
        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) {
@@ -746,8 +793,9 @@ void efi_timer_check(void)
                        continue;
                }
                evt->is_signaled = false;
-               efi_signal_event(evt, true);
+               efi_signal_event(evt);
        }
+       efi_process_event_queue();
        WATCHDOG_RESET();
 }
 
@@ -848,7 +896,7 @@ static efi_status_t EFIAPI efi_wait_for_event(efi_uintn_t num_events,
                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 */
@@ -892,7 +940,7 @@ static efi_status_t EFIAPI efi_signal_event_ext(struct efi_event *event)
        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);
 }
 
@@ -919,13 +967,24 @@ static efi_status_t EFIAPI efi_close_event(struct efi_event *event)
        list_for_each_entry_safe(item, next, &efi_register_notify_events,
                                 link) {
                if (event == item->event) {
+                       struct efi_protocol_notification *hitem, *hnext;
+
+                       /* Remove signaled handles */
+                       list_for_each_entry_safe(hitem, hnext, &item->handles,
+                                                link) {
+                               list_del(&hitem->link);
+                               free(hitem);
+                       }
                        list_del(&item->link);
                        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);
 }
 
@@ -951,7 +1010,7 @@ static efi_status_t EFIAPI efi_check_event(struct efi_event *event)
            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);
@@ -1047,8 +1106,20 @@ efi_status_t efi_add_protocol(const efi_handle_t handle,
 
        /* Notify registered events */
        list_for_each_entry(event, &efi_register_notify_events, link) {
-               if (!guidcmp(protocol, &event->protocol))
-                       efi_signal_event(event->event, true);
+               if (!guidcmp(protocol, &event->protocol)) {
+                       struct efi_protocol_notification *notif;
+
+                       notif = calloc(1, sizeof(*notif));
+                       if (!notif) {
+                               list_del(&handler->link);
+                               free(handler);
+                               return EFI_OUT_OF_RESOURCES;
+                       }
+                       notif->handle = handle;
+                       list_add_tail(&notif->link, &event->handles);
+                       event->event->is_signaled = false;
+                       efi_signal_event(event->event);
+               }
        }
 
        if (!guidcmp(&efi_guid_device_path, protocol))
@@ -1132,11 +1203,15 @@ static efi_status_t efi_get_drivers(efi_handle_t handle,
                                ++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;
@@ -1192,7 +1267,8 @@ static efi_status_t efi_disconnect_all_drivers
                              &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(
@@ -1239,10 +1315,6 @@ static efi_status_t efi_uninstall_protocol
                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 ==
@@ -1330,6 +1402,7 @@ static efi_status_t EFIAPI efi_register_protocol_notify(
 
        item->event = event;
        memcpy(&item->protocol, protocol, sizeof(efi_guid_t));
+       INIT_LIST_HEAD(&item->handles);
 
        list_add_tail(&item->link, &efi_register_notify_events);
 
@@ -1340,9 +1413,9 @@ out:
 
 /**
  * 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.
@@ -1357,7 +1430,6 @@ static int efi_search(enum efi_locate_search_type search_type,
        switch (search_type) {
        case ALL_HANDLES:
                return 0;
-       case BY_REGISTER_NOTIFY:
        case BY_PROTOCOL:
                ret = efi_search_protocol(handle, protocol, NULL);
                return (ret != EFI_SUCCESS);
@@ -1367,6 +1439,27 @@ static int efi_search(enum efi_locate_search_type search_type,
        }
 }
 
+/**
+ * efi_check_register_notify_event() - check if registration key is valid
+ *
+ * Check that a pointer is a valid registration key as returned by
+ * RegisterProtocolNotify().
+ *
+ * @key:       registration key
+ * Return:     valid registration key or NULL
+ */
+static struct efi_register_notify_event *efi_check_register_notify_event
+                                                               (void *key)
+{
+       struct efi_register_notify_event *event;
+
+       list_for_each_entry(event, &efi_register_notify_events, link) {
+               if (event == (struct efi_register_notify_event *)key)
+                       return event;
+       }
+       return NULL;
+}
+
 /**
  * efi_locate_handle() - locate handles implementing a protocol
  *
@@ -1388,7 +1481,8 @@ static efi_status_t efi_locate_handle(
 {
        struct efi_object *efiobj;
        efi_uintn_t size = 0;
-       struct efi_register_notify_event *item, *event = NULL;
+       struct efi_register_notify_event *event;
+       struct efi_protocol_notification *handle = NULL;
 
        /* Check parameters */
        switch (search_type) {
@@ -1398,17 +1492,9 @@ static efi_status_t efi_locate_handle(
                if (!search_key)
                        return EFI_INVALID_PARAMETER;
                /* Check that the registration key is valid */
-               list_for_each_entry(item, &efi_register_notify_events, link) {
-                       if (item ==
-                           (struct efi_register_notify_event *)search_key) {
-                               event = item;
-                               break;
-                       }
-               }
+               event = efi_check_register_notify_event(search_key);
                if (!event)
                        return EFI_INVALID_PARAMETER;
-
-               protocol = &event->protocol;
                break;
        case BY_PROTOCOL:
                if (!protocol)
@@ -1419,14 +1505,23 @@ static efi_status_t efi_locate_handle(
        }
 
        /* Count how much space we need */
-       list_for_each_entry(efiobj, &efi_obj_list, link) {
-               if (!efi_search(search_type, protocol, efiobj))
-                       size += sizeof(void *);
+       if (search_type == BY_REGISTER_NOTIFY) {
+               if (list_empty(&event->handles))
+                       return EFI_NOT_FOUND;
+               handle = list_first_entry(&event->handles,
+                                         struct efi_protocol_notification,
+                                         link);
+               efiobj = handle->handle;
+               size += sizeof(void *);
+       } else {
+               list_for_each_entry(efiobj, &efi_obj_list, link) {
+                       if (!efi_search(search_type, protocol, efiobj))
+                               size += sizeof(void *);
+               }
+               if (size == 0)
+                       return EFI_NOT_FOUND;
        }
 
-       if (size == 0)
-               return EFI_NOT_FOUND;
-
        if (!buffer_size)
                return EFI_INVALID_PARAMETER;
 
@@ -1442,9 +1537,14 @@ static efi_status_t efi_locate_handle(
                return EFI_INVALID_PARAMETER;
 
        /* Then fill the array */
-       list_for_each_entry(efiobj, &efi_obj_list, link) {
-               if (!efi_search(search_type, protocol, efiobj))
-                       *buffer++ = efiobj;
+       if (search_type == BY_REGISTER_NOTIFY) {
+               *buffer = efiobj;
+               list_del(&handle->link);
+       } else {
+               list_for_each_entry(efiobj, &efi_obj_list, link) {
+                       if (!efi_search(search_type, protocol, efiobj))
+                               *buffer++ = efiobj;
+               }
        }
 
        return EFI_SUCCESS;
@@ -1543,7 +1643,7 @@ out:
        /* 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;
                }
        }
@@ -1577,7 +1677,7 @@ static efi_status_t EFIAPI efi_install_configuration_table_ext(efi_guid_t *guid,
  * 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
@@ -1681,7 +1781,7 @@ efi_status_t efi_load_image_from_path(struct efi_device_path *file_path,
        /* Open file */
        f = efi_file_from_path(file_path);
        if (!f)
-               return EFI_DEVICE_ERROR;
+               return EFI_NOT_FOUND;
 
        /* Get file size */
        bs = 0;
@@ -1758,17 +1858,10 @@ efi_status_t EFIAPI efi_load_image(bool boot_policy,
        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;
        }
@@ -1812,13 +1905,21 @@ 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
 }
 
@@ -1841,20 +1942,23 @@ static void efi_exit_caches(void)
 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) {
@@ -1866,15 +1970,28 @@ static efi_status_t EFIAPI efi_exit_boot_services(efi_handle_t image_handle,
                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();
 
@@ -1896,8 +2013,8 @@ static efi_status_t EFIAPI efi_exit_boot_services(efi_handle_t image_handle,
        /* Give the payload some time to boot */
        efi_set_watchdog(0);
        WATCHDOG_RESET();
-
-       return EFI_EXIT(EFI_SUCCESS);
+out:
+       return EFI_EXIT(ret);
 }
 
 /**
@@ -1914,10 +2031,17 @@ static efi_status_t EFIAPI efi_exit_boot_services(efi_handle_t image_handle,
 static efi_status_t EFIAPI efi_get_next_monotonic_count(uint64_t *count)
 {
        static uint64_t mono;
+       efi_status_t ret;
 
        EFI_ENTRY("%p", count);
+       if (!count) {
+               ret = EFI_INVALID_PARAMETER;
+               goto out;
+       }
        *count = mono++;
-       return EFI_EXIT(EFI_SUCCESS);
+       ret = EFI_SUCCESS;
+out:
+       return EFI_EXIT(ret);
 }
 
 /**
@@ -1933,8 +2057,14 @@ static efi_status_t EFIAPI efi_get_next_monotonic_count(uint64_t *count)
  */
 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);
 }
 
@@ -2004,7 +2134,6 @@ static efi_status_t EFIAPI efi_close_protocol(efi_handle_t handle,
                    item->info.controller_handle == controller_handle) {
                        efi_delete_open_info(item);
                        r = EFI_SUCCESS;
-                       break;
                }
        }
 out:
@@ -2203,29 +2332,58 @@ static efi_status_t EFIAPI efi_locate_protocol(const efi_guid_t *protocol,
                                               void *registration,
                                               void **protocol_interface)
 {
-       struct list_head *lhandle;
+       struct efi_handler *handler;
        efi_status_t ret;
+       struct efi_object *efiobj;
 
        EFI_ENTRY("%pUl, %p, %p", protocol, registration, protocol_interface);
 
+       /*
+        * The UEFI spec explicitly requires a protocol even if a registration
+        * key is provided. This differs from the logic in LocateHandle().
+        */
        if (!protocol || !protocol_interface)
                return EFI_EXIT(EFI_INVALID_PARAMETER);
 
-       list_for_each(lhandle, &efi_obj_list) {
-               struct efi_object *efiobj;
-               struct efi_handler *handler;
-
-               efiobj = list_entry(lhandle, struct efi_object, link);
+       if (registration) {
+               struct efi_register_notify_event *event;
+               struct efi_protocol_notification *handle;
 
+               event = efi_check_register_notify_event(registration);
+               if (!event)
+                       return EFI_EXIT(EFI_INVALID_PARAMETER);
+               /*
+                * The UEFI spec requires to return EFI_NOT_FOUND if no
+                * protocol instance matches protocol and registration.
+                * So let's do the same for a mismatch between protocol and
+                * registration.
+                */
+               if (guidcmp(&event->protocol, protocol))
+                       goto not_found;
+               if (list_empty(&event->handles))
+                       goto not_found;
+               handle = list_first_entry(&event->handles,
+                                         struct efi_protocol_notification,
+                                         link);
+               efiobj = handle->handle;
+               list_del(&handle->link);
+               free(handle);
                ret = efi_search_protocol(efiobj, protocol, &handler);
-               if (ret == EFI_SUCCESS) {
-                       *protocol_interface = handler->protocol_interface;
-                       return EFI_EXIT(EFI_SUCCESS);
+               if (ret == EFI_SUCCESS)
+                       goto found;
+       } else {
+               list_for_each_entry(efiobj, &efi_obj_list, link) {
+                       ret = efi_search_protocol(efiobj, protocol, &handler);
+                       if (ret == EFI_SUCCESS)
+                               goto found;
                }
        }
+not_found:
        *protocol_interface = NULL;
-
        return EFI_EXIT(EFI_NOT_FOUND);
+found:
+       *protocol_interface = handler->protocol_interface;
+       return EFI_EXIT(EFI_SUCCESS);
 }
 
 /**
@@ -2332,6 +2490,7 @@ efi_status_t EFIAPI efi_install_multiple_protocol_interfaces
        efi_va_list argptr;
        const efi_guid_t *protocol;
        void *protocol_interface;
+       efi_handle_t old_handle;
        efi_status_t r = EFI_SUCCESS;
        int i = 0;
 
@@ -2344,6 +2503,20 @@ efi_status_t EFIAPI efi_install_multiple_protocol_interfaces
                if (!protocol)
                        break;
                protocol_interface = efi_va_arg(argptr, void*);
+               /* Check that a device path has not been installed before */
+               if (!guidcmp(protocol, &efi_guid_device_path)) {
+                       struct efi_device_path *dp = protocol_interface;
+
+                       r = EFI_CALL(efi_locate_device_path(protocol, &dp,
+                                                           &old_handle));
+                       if (r == EFI_SUCCESS &&
+                           dp->type == DEVICE_PATH_TYPE_END) {
+                               EFI_PRINT("Path %pD already installed\n",
+                                         protocol_interface);
+                               r = EFI_ALREADY_STARTED;
+                               break;
+                       }
+               }
                r = EFI_CALL(efi_install_protocol_interface(
                                                handle, protocol,
                                                EFI_NATIVE_INTERFACE,
@@ -2451,9 +2624,16 @@ static efi_status_t EFIAPI efi_calculate_crc32(const void *data,
                                               efi_uintn_t data_size,
                                               u32 *crc32_p)
 {
+       efi_status_t ret = EFI_SUCCESS;
+
        EFI_ENTRY("%p, %zu", data, data_size);
+       if (!data || !data_size || !crc32_p) {
+               ret = EFI_INVALID_PARAMETER;
+               goto out;
+       }
        *crc32_p = crc32(0, data, data_size);
-       return EFI_EXIT(EFI_SUCCESS);
+out:
+       return EFI_EXIT(ret);
 }
 
 /**
@@ -2530,34 +2710,50 @@ static efi_status_t efi_protocol_open(
                        if ((attributes & EFI_OPEN_PROTOCOL_BY_DRIVER) &&
                            (item->info.attributes == attributes))
                                return EFI_ALREADY_STARTED;
+               } else {
+                       if (item->info.attributes &
+                           EFI_OPEN_PROTOCOL_BY_DRIVER)
+                               opened_by_driver = true;
                }
                if (item->info.attributes & EFI_OPEN_PROTOCOL_EXCLUSIVE)
                        opened_exclusive = true;
        }
 
        /* Only one controller can open the protocol exclusively */
-       if (opened_exclusive && attributes &
-           (EFI_OPEN_PROTOCOL_EXCLUSIVE | EFI_OPEN_PROTOCOL_BY_DRIVER))
-               return EFI_ACCESS_DENIED;
+       if (attributes & EFI_OPEN_PROTOCOL_EXCLUSIVE) {
+               if (opened_exclusive)
+                       return EFI_ACCESS_DENIED;
+       } else if (attributes & EFI_OPEN_PROTOCOL_BY_DRIVER) {
+               if (opened_exclusive || opened_by_driver)
+                       return EFI_ACCESS_DENIED;
+       }
 
        /* Prepare exclusive opening */
        if (attributes & EFI_OPEN_PROTOCOL_EXCLUSIVE) {
                /* Try to disconnect controllers */
+disconnect_next:
+               opened_by_driver = false;
                list_for_each_entry(item, &handler->open_infos, link) {
+                       efi_status_t ret;
+
                        if (item->info.attributes ==
-                                       EFI_OPEN_PROTOCOL_BY_DRIVER)
-                               EFI_CALL(efi_disconnect_controller(
+                                       EFI_OPEN_PROTOCOL_BY_DRIVER) {
+                               ret = EFI_CALL(efi_disconnect_controller(
                                                item->info.controller_handle,
                                                item->info.agent_handle,
                                                NULL));
+                               if (ret == EFI_SUCCESS)
+                                       /*
+                                        * Child controllers may have been
+                                        * removed from the open_infos list. So
+                                        * let's restart the loop.
+                                        */
+                                       goto disconnect_next;
+                               else
+                                       opened_by_driver = true;
+                       }
                }
-               opened_by_driver = false;
-               /* Check if all controllers are disconnected */
-               list_for_each_entry(item, &handler->open_infos, link) {
-                       if (item->info.attributes & EFI_OPEN_PROTOCOL_BY_DRIVER)
-                               opened_by_driver = true;
-               }
-               /* Only one controller can be connected */
+               /* Only one driver can be connected */
                if (opened_by_driver)
                        return EFI_ACCESS_DENIED;
        }
@@ -2565,7 +2761,8 @@ static efi_status_t efi_protocol_open(
        /* 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 */
@@ -2689,14 +2886,15 @@ efi_status_t EFIAPI efi_start_image(efi_handle_t image_handle,
        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;
 
@@ -2749,12 +2947,46 @@ efi_status_t EFIAPI efi_start_image(efi_handle_t image_handle,
  * @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;
 }
 
 /**
@@ -2816,9 +3048,9 @@ out:
 /**
  * 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,
@@ -2954,7 +3186,7 @@ static efi_status_t EFIAPI efi_handle_protocol(efi_handle_t handle,
                                               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);
 }
 
@@ -3020,7 +3252,7 @@ static efi_status_t efi_connect_single_controller(
        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) {
@@ -3127,7 +3359,7 @@ static efi_status_t EFIAPI efi_connect_controller(
                        }
                }
        }
-       /*  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;
@@ -3270,7 +3502,6 @@ static efi_status_t EFIAPI efi_disconnect_controller(
        efi_handle_t *child_handle_buffer = NULL;
        size_t number_of_children = 0;
        efi_status_t r;
-       size_t stop_count = 0;
        struct efi_object *efiobj;
 
        EFI_ENTRY("%p, %p, %p", controller_handle, driver_image_handle,
@@ -3310,32 +3541,35 @@ static efi_status_t EFIAPI efi_disconnect_controller(
                                       (void **)&binding_protocol,
                                       driver_image_handle, NULL,
                                       EFI_OPEN_PROTOCOL_GET_PROTOCOL));
-       if (r != EFI_SUCCESS)
+       if (r != EFI_SUCCESS) {
+               r = EFI_INVALID_PARAMETER;
                goto out;
+       }
        /* Remove the children */
        if (number_of_children) {
                r = EFI_CALL(binding_protocol->stop(binding_protocol,
                                                    controller_handle,
                                                    number_of_children,
                                                    child_handle_buffer));
-               if (r == EFI_SUCCESS)
-                       ++stop_count;
+               if (r != EFI_SUCCESS) {
+                       r = EFI_DEVICE_ERROR;
+                       goto out;
+               }
        }
        /* Remove the driver */
-       if (!child_handle)
+       if (!child_handle) {
                r = EFI_CALL(binding_protocol->stop(binding_protocol,
                                                    controller_handle,
                                                    0, NULL));
-       if (r == EFI_SUCCESS)
-               ++stop_count;
+               if (r != EFI_SUCCESS) {
+                       r = EFI_DEVICE_ERROR;
+                       goto out;
+               }
+       }
        EFI_CALL(efi_close_protocol(driver_image_handle,
                                    &efi_guid_driver_binding_protocol,
                                    driver_image_handle, NULL));
-
-       if (stop_count)
-               r = EFI_SUCCESS;
-       else
-               r = EFI_NOT_FOUND;
+       r = EFI_SUCCESS;
 out:
        if (!child_handle)
                free(child_handle_buffer);
@@ -3406,11 +3640,7 @@ struct efi_system_table __efi_runtime_data systab = {
        },
        .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,
 };
@@ -3430,6 +3660,15 @@ efi_status_t efi_initialize_system_table(void)
                                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);