efi_loader: RegisterProtocolNotify event signaling
[platform/kernel/u-boot.git] / lib / efi_loader / efi_boottime.c
index dd5f706..c7e2ecb 100644 (file)
@@ -1068,6 +1068,7 @@ efi_status_t efi_add_protocol(const efi_handle_t handle,
                        }
                        notif->handle = handle;
                        list_add_tail(&notif->link, &event->handles);
+                       event->event->is_signaled = false;
                        efi_signal_event(event->event, true);
                }
        }
@@ -1153,11 +1154,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;
@@ -1213,7 +1218,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(
@@ -1260,10 +1266,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 ==
@@ -1989,8 +1991,14 @@ out:
  */
 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);
 }
 
@@ -2060,7 +2068,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:
@@ -2259,29 +2266,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);
 }
 
 /**
@@ -2608,34 +2644,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;
        }
@@ -2643,7 +2695,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 */
@@ -2827,12 +2880,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;
 }
 
 /**
@@ -3032,7 +3119,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);
 }