// 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 { 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 { 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> { 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?, } }