blob: cba4dd58700250a47f42007ac8ca1ea91bf32eef [file] [log] [blame] [edit]
/*
* 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;
}