X-Git-Url: https://git.librecmc.org/?a=blobdiff_plain;f=lib%2Fefi_loader%2Fefi_boottime.c;h=f75ca1a67c98a0ba4b9e6f7fc10d15ef0d1ea2ae;hb=6f3badb67dcce2d67ac9abe35b10a04f560bbd08;hp=7d1d6e92138ed297251104b5d2f91cdd888875ab;hpb=5973901826c73462306fbe4051bc3643dca6b88f;p=oweals%2Fu-boot.git diff --git a/lib/efi_loader/efi_boottime.c b/lib/efi_loader/efi_boottime.c index 7d1d6e9213..f75ca1a67c 100644 --- a/lib/efi_loader/efi_boottime.c +++ b/lib/efi_loader/efi_boottime.c @@ -1,8 +1,8 @@ // 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 @@ -25,7 +25,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 +39,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,33 +158,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; - 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(); } /** @@ -211,7 +252,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 @@ -219,8 +259,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; @@ -234,20 +276,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); } } @@ -551,7 +588,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) { @@ -583,6 +620,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 @@ -605,6 +643,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; @@ -617,7 +657,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; @@ -627,9 +670,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; @@ -637,8 +682,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; @@ -733,8 +776,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) { @@ -748,8 +791,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(); } @@ -850,7 +894,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 */ @@ -894,7 +938,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); } @@ -933,9 +977,12 @@ static efi_status_t EFIAPI efi_close_event(struct efi_event *event) 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); } @@ -961,7 +1008,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); @@ -1068,7 +1115,8 @@ efi_status_t efi_add_protocol(const efi_handle_t handle, } 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); } } @@ -1363,9 +1411,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. @@ -1593,7 +1641,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; } } @@ -1627,7 +1675,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 @@ -1731,7 +1779,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; @@ -1808,17 +1856,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; } @@ -1862,13 +1903,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 } @@ -1891,20 +1940,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) { @@ -1916,15 +1968,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(); @@ -1946,8 +2011,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); } /** @@ -2819,14 +2884,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; @@ -2980,9 +3046,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, @@ -3184,7 +3250,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) { @@ -3291,7 +3357,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; @@ -3570,11 +3636,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, }; @@ -3594,6 +3656,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);