| /* | |
| * 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; | |
| size_t max_out_req_length; | |
| 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; | |
| android_boot.max_out_req_length = fastboot_func->out_req->length; | |
| 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) { | |
| // Not initialized or the out request packet is queued and busy. | |
| return EFI_EXIT(EFI_NOT_READY); | |
| } else if (fastboot_func->out_req->status != 0) { | |
| // The out request has encountered an error | |
| return EFI_EXIT(EFI_DEVICE_ERROR); | |
| } else if (fastboot_func->out_req->actual) { | |
| // Has available data. | |
| if (*buffer_size < (size_t)fastboot_func->out_req->actual) { | |
| // We don't support partial read, the caller has to read the whole | |
| // packet it previously requested. | |
| *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; | |
| // Clear the `actual` field to indicate idleness. | |
| fastboot_func->out_req->actual = 0; | |
| return EFI_EXIT(EFI_SUCCESS); | |
| } else { | |
| // Usb receive request is not queued yet. Queue it. | |
| // Adjusts the request length to be the smaller between the maximum | |
| // length and the requested read size, round up to the multiple of max | |
| // packet size. This is necessary otherwise if host sends smaller than | |
| // length amount of data, and it is a multiple of max packet size, | |
| // driver hangs. | |
| size_t length = | |
| min(*buffer_size, android_boot.max_out_req_length); | |
| length = (length + (fastboot_func->out_ep->maxpacket - 1)) / | |
| fastboot_func->out_ep->maxpacket * | |
| fastboot_func->out_ep->maxpacket; | |
| fastboot_func->out_req->length = length; | |
| if (usb_ep_queue(fastboot_func->out_ep, fastboot_func->out_req, | |
| 0)) { | |
| return EFI_EXIT(EFI_DEVICE_ERROR); | |
| } | |
| return EFI_EXIT(EFI_NOT_READY); | |
| } | |
| } | |
| 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; | |
| } |