| /* |
| * Copyright (c) 2025 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 <gbl_efi_fastboot_transport.h> |
| #include <g_dnl.h> |
| #include <fastboot_usb.h> |
| |
| DECLARE_GLOBAL_DATA_PTR; |
| |
| #define REQ_BUFFER_SIZE EP_BUFFER_SIZE |
| |
| #define MIN(a, b) ((a) < (b) ? (a) : (b)) |
| #define MAX(a, b) ((a) > (b) ? (a) : (b)) |
| |
| static void *rx_buffer_a = NULL; // For driver to write. |
| static void *rx_buffer_b = NULL; // For copying to user. |
| |
| void swap_rx_buffer(void) |
| { |
| void *temp = rx_buffer_a; |
| rx_buffer_a = rx_buffer_b; |
| rx_buffer_b = temp; |
| } |
| |
| // 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 start(GblEfiFastbootTransportProtocol *self) |
| { |
| EFI_ENTRY("%p", self); |
| |
| if (is_started()) { |
| return EFI_EXIT(EFI_ALREADY_STARTED); |
| } |
| |
| 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); |
| } |
| |
| void **buffers[] = { &rx_buffer_a, &rx_buffer_b }; |
| for (size_t i = 0; i < ARRAY_SIZE(buffers); i++) { |
| if (*buffers[i]) { |
| continue; |
| } |
| |
| *buffers[i] = |
| memalign(CONFIG_SYS_CACHELINE_SIZE, REQ_BUFFER_SIZE); |
| if (!*buffers[i]) { |
| printf("Failed to allocate RX transfer buffer"); |
| return EFI_EXIT(EFI_OUT_OF_RESOURCES); |
| } |
| } |
| |
| while (!fastboot_func->out_req || !fastboot_func->in_req) { |
| usb_gadget_handle_interrupts(); |
| } |
| |
| // Queue initial request. |
| // |
| // receive() queues the next RX packet pro-actively upon receiving a packet. |
| // Thus the initial request needs to be queued first. Queuing it here makes |
| // receive() a little cleaner. |
| fastboot_func->out_req->actual = 0; |
| fastboot_func->out_req->length = fastboot_func->out_ep->maxpacket; |
| fastboot_func->out_req->buf = rx_buffer_a; |
| int res = |
| usb_ep_queue(fastboot_func->out_ep, fastboot_func->out_req, 0); |
| if (res) { |
| printf("Failed to queue initial request, %d\n", res); |
| return EFI_EXIT(EFI_DEVICE_ERROR); |
| } |
| |
| return EFI_EXIT(EFI_SUCCESS); |
| } |
| |
| static efi_status_t EFIAPI stop(GblEfiFastbootTransportProtocol *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 receive(GblEfiFastbootTransportProtocol *self, |
| size_t *buffer_size, void *buffer, |
| GblEfiFastbootRxMode mode) |
| { |
| EFI_ENTRY("%p, %p", buffer_size, buffer); |
| |
| if (!is_started()) { |
| return EFI_EXIT(EFI_NOT_STARTED); |
| } else if (!self || !buffer_size || !buffer) { |
| return EFI_EXIT(EFI_INVALID_PARAMETER); |
| } |
| |
| usb_gadget_handle_interrupts(); |
| // Executes any event check and notification payload. |
| 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) { |
| size_t actual = (size_t)fastboot_func->out_req->actual; |
| // Has available data. |
| if (*buffer_size < actual) { |
| // We don't support partial read, the caller has to read the whole |
| // packet it previously requested. |
| *buffer_size = actual; |
| return EFI_EXIT(EFI_BUFFER_TOO_SMALL); |
| } |
| swap_rx_buffer(); |
| // Queue the next packet first so that receive of the next packet and |
| // copying of current copy can be done in parallel. |
| size_t next_size = |
| mode == SINGLE_PACKET ? |
| fastboot_func->out_ep->maxpacket : |
| MAX(MIN(*buffer_size - actual, EP_BUFFER_SIZE), |
| fastboot_func->out_ep->maxpacket); |
| *buffer_size = (size_t)fastboot_func->out_req->actual; |
| fastboot_func->out_req->actual = 0; |
| fastboot_func->out_req->length = next_size; |
| fastboot_func->out_req->buf = rx_buffer_a; |
| if (usb_ep_queue(fastboot_func->out_ep, fastboot_func->out_req, |
| 0)) { |
| printf("Failed to queue nextrequest\n"); |
| return EFI_EXIT(EFI_DEVICE_ERROR); |
| } |
| usb_gadget_handle_interrupts(); |
| memcpy(buffer, rx_buffer_b, *buffer_size); |
| } |
| return EFI_EXIT(EFI_SUCCESS); |
| } |
| |
| static efi_status_t EFIAPI send(GblEfiFastbootTransportProtocol *self, |
| size_t *buffer_size, const void *buffer) |
| { |
| EFI_ENTRY("%p, %p", buffer_size, buffer); |
| |
| if (!is_started()) { |
| return EFI_EXIT(EFI_NOT_STARTED); |
| } |
| |
| usb_gadget_handle_interrupts(); |
| // Executes any event check and notification payload. |
| 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); |
| } |
| |
| *buffer_size = MIN(*buffer_size, REQ_BUFFER_SIZE); |
| 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)) { |
| printf("Failed to queue send request"); |
| return EFI_EXIT(EFI_DEVICE_ERROR); |
| } |
| return EFI_EXIT(EFI_SUCCESS); |
| } |
| |
| static efi_status_t EFIAPI flush(GblEfiFastbootTransportProtocol *self) |
| { |
| EFI_ENTRY("%p", self); |
| |
| if (!is_started()) { |
| return EFI_EXIT(EFI_NOT_STARTED); |
| } |
| efi_timer_check(); |
| |
| while (!fastboot_func->in_req || |
| fastboot_func->in_req->status == -EINPROGRESS) { |
| usb_gadget_handle_interrupts(); |
| efi_timer_check(); |
| } |
| return EFI_EXIT(EFI_SUCCESS); |
| } |
| |
| static const efi_guid_t guid = GBL_EFI_FASTBOOT_TRANSPORT_PROTOCOL_GUID; |
| |
| static GblEfiFastbootTransportProtocol protocol = { |
| .revision = GBL_EFI_FASTBOOT_TRANSPORT_PROTOCOL_REVISION, |
| .description = "Vim3 Fastboot USB", |
| .start = start, |
| .stop = stop, |
| .receive = receive, |
| .send = send, |
| .flush = flush, |
| }; |
| |
| // Registers GBL_EFI_FASTBOOT_TRANSPORT protocol. |
| efi_status_t efi_gbl_fastboot_transport_register(void) |
| { |
| efi_status_t ret = efi_add_protocol(efi_root, &guid, (void *)&protocol); |
| if (ret != EFI_SUCCESS) |
| printf("Cannot install EFI_ANDROID_BOOT_PROTOCOL\n"); |
| return EFI_SUCCESS; |
| } |