blob: 0040e4b3f5705570a83c3919aa56d8c66a9e9dd8 [file] [log] [blame]
// Copyright 2024, The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// This EFI application implements a demo for booting Android/Fuchsia from disk. See
// bootable/libbootloader/gbl/README.md for how to run the demo. See comments of
// `android_boot:android_boot_demo()` and `fuchsia_boot:fuchsia_boot_demo()` for
// supported/unsupported features at the moment.
use crate::error::{EfiAppError, GblEfiError, Result as GblResult};
use crate::net::{with_efi_network, EfiTcpSocket};
use crate::utils::{find_gpt_devices, loop_with_timeout};
use core::{fmt::Write, result::Result};
use efi::{
defs::{EFI_STATUS_NOT_READY, EFI_STATUS_NOT_STARTED},
efi_print, efi_println,
protocol::{android_boot::AndroidBootProtocol, Protocol},
EfiEntry,
};
use fastboot::{Fastboot, TcpStream, Transport, TransportError};
use libgbl::fastboot::GblFastboot;
const DEFAULT_TIMEOUT_MS: u64 = 5_000;
const FASTBOOT_TCP_PORT: u16 = 5554;
struct EfiFastbootTcpTransport<'a, 'b, 'c> {
last_err: GblResult<()>,
socket: &'c mut EfiTcpSocket<'a, 'b>,
}
impl<'a, 'b, 'c> EfiFastbootTcpTransport<'a, 'b, 'c> {
fn new(socket: &'c mut EfiTcpSocket<'a, 'b>) -> Self {
Self { last_err: Ok(()), socket: socket }
}
}
impl TcpStream for EfiFastbootTcpTransport<'_, '_, '_> {
/// Reads to `out` for exactly `out.len()` number bytes from the TCP connection.
fn read_exact(&mut self, out: &mut [u8]) -> Result<(), TransportError> {
self.last_err = self.socket.receive_exact(out, DEFAULT_TIMEOUT_MS);
self.last_err.as_ref().map_err(|_| TransportError::Others("TCP read error"))?;
Ok(())
}
/// Sends exactly `data.len()` number bytes from `data` to the TCP connection.
fn write_exact(&mut self, data: &[u8]) -> Result<(), TransportError> {
self.last_err = self.socket.send_exact(data, DEFAULT_TIMEOUT_MS);
self.last_err.as_ref().map_err(|_| TransportError::Others("TCP write error"))?;
Ok(())
}
}
/// `UsbTransport` implements the `fastboot::Transport` trait using USB interfaces from
/// EFI_ANDROID_BOOT_PROTOCOL.
pub struct UsbTransport<'a, 'b> {
last_err: GblResult<()>,
max_packet_size: usize,
protocol: &'b Protocol<'a, AndroidBootProtocol>,
}
impl<'a, 'b> UsbTransport<'a, 'b> {
fn new(max_packet_size: usize, protocol: &'b Protocol<'a, AndroidBootProtocol>) -> Self {
Self { last_err: Ok(()), max_packet_size: max_packet_size, protocol: protocol }
}
/// Waits for the previous send to complete up to `DEFAULT_TIMEOUT_MS` timeout.
fn wait_for_send(&self) -> GblResult<()> {
loop_with_timeout(self.protocol.efi_entry(), DEFAULT_TIMEOUT_MS, || {
match (|| -> GblResult<bool> {
Ok(self
.protocol
.efi_entry()
.system_table()
.boot_services()
.check_event(&self.protocol.wait_for_send_completion()?)?)
})() {
Ok(true) => Ok(Ok(())),
Ok(false) => Err(false),
Err(e) => Ok(Err(e)),
}
})?
.ok_or(EfiAppError::Timeout)??;
Ok(())
}
}
impl Transport for UsbTransport<'_, '_> {
fn receive_packet(&mut self, out: &mut [u8]) -> Result<usize, TransportError> {
let mut out_size = 0;
self.last_err = Ok(());
match self.protocol.fastboot_usb_receive(out, &mut out_size) {
Ok(()) => Ok(out_size),
Err(e) if e.is_efi_err(EFI_STATUS_NOT_READY) => Ok(0),
Err(e) => {
self.last_err = Err(e.into());
Err(TransportError::Others("USB receive error"))
}
}
}
fn send_packet(&mut self, packet: &[u8]) -> Result<(), TransportError> {
let mut sent = 0;
self.last_err = (|| -> GblResult<()> {
while sent < packet.len() {
let to_send = core::cmp::min(packet.len() - sent, self.max_packet_size);
let mut out_size = 0;
self.protocol.fastboot_usb_send(&packet[sent..][..to_send], &mut out_size)?;
self.wait_for_send()?;
sent += to_send;
}
Ok(())
})();
Ok(*self.last_err.as_ref().map_err(|_| TransportError::Others("USB send error"))?)
}
}
/// Loops and polls both USB and TCP transport. Runs Fastboot if any is available.
fn fastboot_loop(
efi_entry: &EfiEntry,
gbl_fb: &mut GblFastboot,
fastboot: &mut Fastboot,
mut socket: Option<&mut EfiTcpSocket>,
mut usb: Option<&mut UsbTransport>,
) -> GblResult<()> {
if socket.is_none() && usb.is_none() {
return Err(EfiAppError::Unsupported.into());
}
efi_println!(efi_entry, "Fastboot USB: {}", usb.as_ref().map_or("No", |_| "Yes"));
if let Some(socket) = socket.as_ref() {
efi_println!(efi_entry, "Fastboot TCP: Yes");
efi_println!(efi_entry, "Device IP addresses:");
socket.interface().ip_addrs().iter().for_each(|v| {
efi_println!(efi_entry, "\t{}", v.address());
});
} else {
efi_println!(efi_entry, "Fastboot TCP: No");
}
let mut listen_start_timestamp = EfiTcpSocket::timestamp(0);
loop {
// Checks and processes commands over USB.
if let Some(usb) = usb.as_mut() {
if fastboot.process_next_command(*usb, gbl_fb).is_err() {
efi_println!(efi_entry, "Fastboot USB error: {:?}", usb.last_err);
}
}
// Checks and processes commands over TCP.
if let Some(socket) = socket.as_mut() {
socket.poll();
let mut reset_socket = false;
if socket.check_active() {
let remote = socket.get_socket().remote_endpoint().unwrap();
efi_println!(efi_entry, "TCP connection from {}", remote);
let mut transport = EfiFastbootTcpTransport::new(socket);
let _ = fastboot.run_tcp_session(&mut transport, gbl_fb);
match transport.last_err {
Ok(()) | Err(GblEfiError::EfiAppError(EfiAppError::PeerClosed)) => {}
Err(e) => {
efi_println!(efi_entry, "Fastboot TCP error {:?}", e);
}
}
reset_socket = true;
} else if EfiTcpSocket::timestamp(listen_start_timestamp) > DEFAULT_TIMEOUT_MS {
// Reset once in a while in case a remote client disconnects in the middle of
// TCP handshake and leaves the socket in a half open state.
reset_socket = true;
}
if reset_socket {
listen_start_timestamp = EfiTcpSocket::timestamp(0);
if let Err(e) = socket.listen(FASTBOOT_TCP_PORT) {
efi_println!(efi_entry, "TCP listen error: {:?}", e);
}
}
}
}
}
/// Initializes the Fastboot USB interface and returns a `UsbTransport`.
fn init_usb<'a, 'b>(
android_boot_protocol: &Option<&'b Protocol<'a, AndroidBootProtocol>>,
) -> GblResult<UsbTransport<'a, 'b>> {
let protocol = android_boot_protocol.ok_or(EfiAppError::Unsupported)?;
match protocol.fastboot_usb_interface_stop() {
Err(e) if !e.is_efi_err(EFI_STATUS_NOT_STARTED) => return Err(e.into()),
_ => {}
};
Ok(UsbTransport::new(protocol.fastboot_usb_interface_start()?, protocol))
}
/// Runs Fastboot.
pub fn run_fastboot(
efi_entry: &EfiEntry,
android_boot_protocol: Option<&Protocol<'_, AndroidBootProtocol>>,
) -> GblResult<()> {
let mut gpt_devices = find_gpt_devices(efi_entry)?;
let mut gbl_fb = GblFastboot::new(&mut gpt_devices);
// TODO(b/328786603): Figure out where to get download buffer size.
let mut download_buffer = vec![0u8; 512 * 1024 * 1024];
let mut fastboot = Fastboot::new(&mut download_buffer[..]);
let mut usb = match init_usb(&android_boot_protocol) {
Ok(v) => Some(v),
Err(e) => {
efi_println!(efi_entry, "Failed to start Fastboot over USB. {:?}.", e);
None
}
};
match with_efi_network(efi_entry, |socket| -> GblResult<()> {
fastboot_loop(efi_entry, &mut gbl_fb, &mut fastboot, Some(socket), usb.as_mut())
}) {
Err(e) => {
efi_println!(efi_entry, "Failed to start EFI network. {:?}.", e);
fastboot_loop(efi_entry, &mut gbl_fb, &mut fastboot, None, usb.as_mut())
}
v => v?,
}
}