| /* |
| * Copyright (c) 2024 The Fuchsia Authors |
| * |
| * SPDX-License-Identifier: BSD-3-Clause |
| */ |
| |
| #include <common.h> |
| #include <efi_loader.h> |
| #include <log.h> |
| #include <asm/global_data.h> |
| #include <efi_android_boot.h> |
| #include <g_dnl.h> |
| #include <fastboot_usb.h> |
| |
| DECLARE_GLOBAL_DATA_PTR; |
| |
| static const efi_guid_t efi_guid_android_boot_protocol = |
| EFI_ANDROID_BOOT_PROTOCOL_GUID; |
| |
| static struct android_boot_protocol_instance { |
| efi_android_boot_protocol protocol; |
| struct efi_event *timer_event; |
| bool send_completion_already_signaled; |
| } android_boot; |
| |
| // USB driver requires a mandatory callback. We simply use a noop placeholder. |
| // The implementation polls send/receive status directly. |
| static void noop_cb(struct usb_ep *, struct usb_request *) |
| { |
| } |
| |
| // Checks whether Fastboot USB endpoints are allocated. |
| static bool is_started(void) |
| { |
| return fastboot_func && fastboot_func->in_ep && fastboot_func->out_ep; |
| } |
| |
| static efi_status_t EFIAPI fastboot_usb_interface_start( |
| struct efi_android_boot_protocol *self, size_t *max_packet_size) |
| { |
| EFI_ENTRY("%p, %p", self, max_packet_size); |
| |
| if (is_started()) { |
| return EFI_EXIT(EFI_ALREADY_STARTED); |
| } else if (!max_packet_size) { |
| return EFI_EXIT(EFI_INVALID_PARAMETER); |
| } |
| |
| int ret = set_fastboot_usb_completion_cb(noop_cb, noop_cb); |
| if (ret) { |
| printf("usb_dnl_fastboot failed to set cb: %d\n", ret); |
| return EFI_EXIT(EFI_DEVICE_ERROR); |
| } |
| |
| ret = g_dnl_register("usb_dnl_fastboot"); |
| if (ret) { |
| printf("usb_dnl_fastboot register failed: %d\n", ret); |
| return EFI_EXIT(EFI_DEVICE_ERROR); |
| } |
| |
| if (!is_started()) { |
| printf("Fastboot end points not allocated\n"); |
| return EFI_EXIT(EFI_DEVICE_ERROR); |
| } |
| |
| *max_packet_size = fastboot_func->in_ep->maxpacket; |
| return EFI_EXIT(EFI_SUCCESS); |
| } |
| |
| static efi_status_t EFIAPI |
| fastboot_usb_interface_stop(struct efi_android_boot_protocol *self) |
| { |
| EFI_ENTRY("%p", self); |
| |
| if (!is_started()) { |
| return EFI_EXIT(EFI_NOT_STARTED); |
| } |
| |
| g_dnl_unregister(); |
| run_command("usb reset", 0); |
| reset_fastboot_usb_completion_cb(); |
| return EFI_EXIT(EFI_SUCCESS); |
| } |
| |
| static efi_status_t EFIAPI |
| fastboot_usb_receive(struct efi_android_boot_protocol *self, |
| size_t *buffer_size, void *buffer) |
| { |
| EFI_ENTRY("%p, %p", buffer_size, buffer); |
| |
| if (!is_started()) { |
| return EFI_EXIT(EFI_NOT_STARTED); |
| } |
| |
| // Executes any event check and notification payload. This includes our |
| // `efi_fastboot_usb_timer_notify` notify function. |
| efi_timer_check(); |
| |
| if (!fastboot_func->out_req || |
| fastboot_func->out_req->status == -EINPROGRESS) { |
| return EFI_EXIT(EFI_NOT_READY); |
| } else if (fastboot_func->out_req->status != 0) { |
| return EFI_EXIT(EFI_DEVICE_ERROR); |
| } else if (*buffer_size < (size_t)fastboot_func->out_req->actual) { |
| *buffer_size = (size_t)fastboot_func->out_req->actual; |
| return EFI_EXIT(EFI_BUFFER_TOO_SMALL); |
| } |
| |
| memcpy(buffer, fastboot_func->out_req->buf, |
| (size_t)fastboot_func->out_req->actual); |
| *buffer_size = (size_t)fastboot_func->out_req->actual; |
| // Queues back the USB rx request to receive the next packet. |
| if (usb_ep_queue(fastboot_func->out_ep, fastboot_func->out_req, 0)) { |
| return EFI_EXIT(EFI_DEVICE_ERROR); |
| } |
| return EFI_EXIT(EFI_SUCCESS); |
| } |
| |
| static efi_status_t EFIAPI |
| fastboot_usb_send(struct efi_android_boot_protocol *self, size_t *buffer_size, |
| const void *buffer) |
| { |
| EFI_ENTRY("%p, %p", buffer_size, buffer); |
| |
| if (!is_started()) { |
| return EFI_EXIT(EFI_NOT_STARTED); |
| } |
| |
| // Executes any event check and notification payload. This includes our |
| // `efi_fastboot_usb_timer_notify` notify function. |
| efi_timer_check(); |
| |
| if (!fastboot_func->in_req && |
| fastboot_func->in_req->status == -EINPROGRESS) { |
| return EFI_EXIT(EFI_NOT_READY); |
| } else if (fastboot_func->in_req->status != 0) { |
| return EFI_EXIT(EFI_DEVICE_ERROR); |
| } else if (*buffer_size > (size_t)fastboot_func->in_ep->maxpacket) { |
| *buffer_size = (size_t)fastboot_func->in_ep->maxpacket; |
| return EFI_EXIT(EFI_BAD_BUFFER_SIZE); |
| } |
| |
| // Resets event signal state. |
| android_boot.send_completion_already_signaled = false; |
| self->wait_for_send_completion->is_signaled = false; |
| memcpy(fastboot_func->in_req->buf, buffer, *buffer_size); |
| fastboot_func->in_req->length = *buffer_size; |
| if (usb_ep_queue(fastboot_func->in_ep, fastboot_func->in_req, 0)) { |
| return EFI_EXIT(EFI_DEVICE_ERROR); |
| } |
| return EFI_EXIT(EFI_SUCCESS); |
| } |
| |
| static struct android_boot_protocol_instance android_boot = { |
| .protocol = { |
| .revision = EFI_ANDROID_BOOT_PROTOCOL_REVISION, |
| .fastboot_usb_interface_start = fastboot_usb_interface_start, |
| .fastboot_usb_interface_stop = fastboot_usb_interface_stop, |
| .fastboot_usb_receive = fastboot_usb_receive, |
| .fastboot_usb_send = fastboot_usb_send, |
| .wait_for_send_completion = NULL, |
| }, |
| .timer_event = NULL, |
| .send_completion_already_signaled = false, |
| }; |
| |
| // A timer event notification function that polls send/receive status and |
| // signals event. |
| static void EFIAPI efi_fastboot_usb_timer_notify(struct efi_event *event, |
| void *context) |
| { |
| EFI_ENTRY("%p, %p", event, context); |
| efi_android_boot_protocol *protocol = &android_boot.protocol; |
| if (!is_started() || !protocol->wait_for_send_completion) { |
| EFI_EXIT(EFI_SUCCESS); |
| return; |
| } |
| |
| usb_gadget_handle_interrupts(); |
| if (fastboot_func->in_req->status == 0 && |
| !android_boot.send_completion_already_signaled) { |
| android_boot.send_completion_already_signaled = true; |
| protocol->wait_for_send_completion->is_signaled = true; |
| } |
| EFI_EXIT(EFI_SUCCESS); |
| } |
| |
| static void EFIAPI efi_fastboot_usb_wait_notify(struct efi_event *, void *) |
| { |
| // Nothing to do for `wait_for_send_completion` notificiaiton. But UEFI |
| // still requires it. |
| } |
| |
| // Registers EFI_ANDROID_BOOT_PROTOCOL |
| efi_status_t efi_android_boot_register(void) |
| { |
| efi_status_t ret = efi_add_protocol(efi_root, |
| &efi_guid_android_boot_protocol, |
| (void *)&android_boot.protocol); |
| if (ret != EFI_SUCCESS) |
| printf("Cannot install EFI_ANDROID_BOOT_PROTOCOL\n"); |
| |
| // Create a `wait_for_send_completion` event. |
| ret = efi_create_event(EVT_NOTIFY_WAIT, TPL_CALLBACK, |
| efi_fastboot_usb_wait_notify, NULL, NULL, |
| &android_boot.protocol.wait_for_send_completion); |
| if (ret != EFI_SUCCESS) { |
| printf("Failed to create wait_for_send_completion event\n"); |
| return ret; |
| } |
| |
| // Create a timer event so that the notify function can get triggered |
| // whenever the EFI boot service processes any event. |
| ret = efi_create_event(EVT_TIMER | EVT_NOTIFY_SIGNAL, TPL_NOTIFY, |
| efi_fastboot_usb_timer_notify, NULL, NULL, |
| &android_boot.timer_event); |
| if (ret != EFI_SUCCESS) { |
| printf("Failed to add EFI_ANDROID_BOOT_PROTOCOL timer event\n"); |
| return ret; |
| } |
| // Fastboot USB is time critical, Triggers the event in every timer cycle |
| // Set timer period to 0. This is equivalent to invoking |
| // `efi_fastboot_usb_timer_notify` every time boot service check any event |
| // i.e. in `efi_timer_check()` |
| ret = efi_set_timer(android_boot.timer_event, EFI_TIMER_PERIODIC, 0); |
| if (ret != EFI_SUCCESS) { |
| printf("Failed to set EFI_ANDROID_BOOT_PROTOCOL timer\n"); |
| return ret; |
| } |
| |
| return EFI_SUCCESS; |
| } |