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