1 // Copyright 2024, The Android Open Source Project
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //     http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 // This EFI application implements a demo for booting Android/Fuchsia from disk. See
16 // bootable/libbootloader/gbl/README.md for how to run the demo. See comments of
17 // `android_boot:android_boot_demo()` and `fuchsia_boot:fuchsia_boot_demo()` for
18 // supported/unsupported features at the moment.
19 
20 use crate::error::{EfiAppError, GblEfiError, Result as GblResult};
21 use crate::net::{with_efi_network, EfiTcpSocket};
22 use crate::utils::{find_gpt_devices, loop_with_timeout};
23 use core::{fmt::Write, result::Result};
24 use efi::{
25     defs::{EFI_STATUS_NOT_READY, EFI_STATUS_NOT_STARTED},
26     efi_print, efi_println,
27     protocol::{android_boot::AndroidBootProtocol, Protocol},
28     EfiEntry,
29 };
30 use fastboot::{Fastboot, TcpStream, Transport, TransportError};
31 use libgbl::fastboot::GblFastboot;
32 
33 const DEFAULT_TIMEOUT_MS: u64 = 5_000;
34 const FASTBOOT_TCP_PORT: u16 = 5554;
35 
36 struct EfiFastbootTcpTransport<'a, 'b, 'c> {
37     last_err: GblResult<()>,
38     socket: &'c mut EfiTcpSocket<'a, 'b>,
39 }
40 
41 impl<'a, 'b, 'c> EfiFastbootTcpTransport<'a, 'b, 'c> {
new(socket: &'c mut EfiTcpSocket<'a, 'b>) -> Self42     fn new(socket: &'c mut EfiTcpSocket<'a, 'b>) -> Self {
43         Self { last_err: Ok(()), socket: socket }
44     }
45 }
46 
47 impl TcpStream for EfiFastbootTcpTransport<'_, '_, '_> {
48     /// Reads to `out` for exactly `out.len()` number bytes from the TCP connection.
read_exact(&mut self, out: &mut [u8]) -> Result<(), TransportError>49     fn read_exact(&mut self, out: &mut [u8]) -> Result<(), TransportError> {
50         self.last_err = self.socket.receive_exact(out, DEFAULT_TIMEOUT_MS);
51         self.last_err.as_ref().map_err(|_| TransportError::Others("TCP read error"))?;
52         Ok(())
53     }
54 
55     /// Sends exactly `data.len()` number bytes from `data` to the TCP connection.
write_exact(&mut self, data: &[u8]) -> Result<(), TransportError>56     fn write_exact(&mut self, data: &[u8]) -> Result<(), TransportError> {
57         self.last_err = self.socket.send_exact(data, DEFAULT_TIMEOUT_MS);
58         self.last_err.as_ref().map_err(|_| TransportError::Others("TCP write error"))?;
59         Ok(())
60     }
61 }
62 
63 /// `UsbTransport` implements the `fastboot::Transport` trait using USB interfaces from
64 /// EFI_ANDROID_BOOT_PROTOCOL.
65 pub struct UsbTransport<'a, 'b> {
66     last_err: GblResult<()>,
67     max_packet_size: usize,
68     protocol: &'b Protocol<'a, AndroidBootProtocol>,
69 }
70 
71 impl<'a, 'b> UsbTransport<'a, 'b> {
new(max_packet_size: usize, protocol: &'b Protocol<'a, AndroidBootProtocol>) -> Self72     fn new(max_packet_size: usize, protocol: &'b Protocol<'a, AndroidBootProtocol>) -> Self {
73         Self { last_err: Ok(()), max_packet_size: max_packet_size, protocol: protocol }
74     }
75 
76     /// Waits for the previous send to complete up to `DEFAULT_TIMEOUT_MS` timeout.
wait_for_send(&self) -> GblResult<()>77     fn wait_for_send(&self) -> GblResult<()> {
78         loop_with_timeout(self.protocol.efi_entry(), DEFAULT_TIMEOUT_MS, || {
79             match (|| -> GblResult<bool> {
80                 Ok(self
81                     .protocol
82                     .efi_entry()
83                     .system_table()
84                     .boot_services()
85                     .check_event(&self.protocol.wait_for_send_completion()?)?)
86             })() {
87                 Ok(true) => Ok(Ok(())),
88                 Ok(false) => Err(false),
89                 Err(e) => Ok(Err(e)),
90             }
91         })?
92         .ok_or(EfiAppError::Timeout)??;
93         Ok(())
94     }
95 }
96 
97 impl Transport for UsbTransport<'_, '_> {
receive_packet(&mut self, out: &mut [u8]) -> Result<usize, TransportError>98     fn receive_packet(&mut self, out: &mut [u8]) -> Result<usize, TransportError> {
99         let mut out_size = 0;
100         self.last_err = Ok(());
101         match self.protocol.fastboot_usb_receive(out, &mut out_size) {
102             Ok(()) => Ok(out_size),
103             Err(e) if e.is_efi_err(EFI_STATUS_NOT_READY) => Ok(0),
104             Err(e) => {
105                 self.last_err = Err(e.into());
106                 Err(TransportError::Others("USB receive error"))
107             }
108         }
109     }
110 
send_packet(&mut self, packet: &[u8]) -> Result<(), TransportError>111     fn send_packet(&mut self, packet: &[u8]) -> Result<(), TransportError> {
112         let mut sent = 0;
113         self.last_err = (|| -> GblResult<()> {
114             while sent < packet.len() {
115                 let to_send = core::cmp::min(packet.len() - sent, self.max_packet_size);
116                 let mut out_size = 0;
117                 self.protocol.fastboot_usb_send(&packet[sent..][..to_send], &mut out_size)?;
118                 self.wait_for_send()?;
119                 sent += to_send;
120             }
121             Ok(())
122         })();
123         Ok(*self.last_err.as_ref().map_err(|_| TransportError::Others("USB send error"))?)
124     }
125 }
126 
127 /// Loops and polls both USB and TCP transport. Runs Fastboot if any is available.
fastboot_loop( efi_entry: &EfiEntry, gbl_fb: &mut GblFastboot, fastboot: &mut Fastboot, mut socket: Option<&mut EfiTcpSocket>, mut usb: Option<&mut UsbTransport>, ) -> GblResult<()>128 fn fastboot_loop(
129     efi_entry: &EfiEntry,
130     gbl_fb: &mut GblFastboot,
131     fastboot: &mut Fastboot,
132     mut socket: Option<&mut EfiTcpSocket>,
133     mut usb: Option<&mut UsbTransport>,
134 ) -> GblResult<()> {
135     if socket.is_none() && usb.is_none() {
136         return Err(EfiAppError::Unsupported.into());
137     }
138 
139     efi_println!(efi_entry, "Fastboot USB: {}", usb.as_ref().map_or("No", |_| "Yes"));
140     if let Some(socket) = socket.as_ref() {
141         efi_println!(efi_entry, "Fastboot TCP: Yes");
142         efi_println!(efi_entry, "Device IP addresses:");
143         socket.interface().ip_addrs().iter().for_each(|v| {
144             efi_println!(efi_entry, "\t{}", v.address());
145         });
146     } else {
147         efi_println!(efi_entry, "Fastboot TCP: No");
148     }
149 
150     let mut listen_start_timestamp = EfiTcpSocket::timestamp(0);
151     loop {
152         // Checks and processes commands over USB.
153         if let Some(usb) = usb.as_mut() {
154             if fastboot.process_next_command(*usb, gbl_fb).is_err() {
155                 efi_println!(efi_entry, "Fastboot USB error: {:?}", usb.last_err);
156             }
157         }
158 
159         // Checks and processes commands over TCP.
160         if let Some(socket) = socket.as_mut() {
161             socket.poll();
162             let mut reset_socket = false;
163             if socket.check_active() {
164                 let remote = socket.get_socket().remote_endpoint().unwrap();
165                 efi_println!(efi_entry, "TCP connection from {}", remote);
166                 let mut transport = EfiFastbootTcpTransport::new(socket);
167                 let _ = fastboot.run_tcp_session(&mut transport, gbl_fb);
168                 match transport.last_err {
169                     Ok(()) | Err(GblEfiError::EfiAppError(EfiAppError::PeerClosed)) => {}
170                     Err(e) => {
171                         efi_println!(efi_entry, "Fastboot TCP error {:?}", e);
172                     }
173                 }
174                 reset_socket = true;
175             } else if EfiTcpSocket::timestamp(listen_start_timestamp) > DEFAULT_TIMEOUT_MS {
176                 // Reset once in a while in case a remote client disconnects in the middle of
177                 // TCP handshake and leaves the socket in a half open state.
178                 reset_socket = true;
179             }
180 
181             if reset_socket {
182                 listen_start_timestamp = EfiTcpSocket::timestamp(0);
183                 if let Err(e) = socket.listen(FASTBOOT_TCP_PORT) {
184                     efi_println!(efi_entry, "TCP listen error: {:?}", e);
185                 }
186             }
187         }
188     }
189 }
190 
191 /// Initializes the Fastboot USB interface and returns a `UsbTransport`.
init_usb<'a, 'b>( android_boot_protocol: &Option<&'b Protocol<'a, AndroidBootProtocol>>, ) -> GblResult<UsbTransport<'a, 'b>>192 fn init_usb<'a, 'b>(
193     android_boot_protocol: &Option<&'b Protocol<'a, AndroidBootProtocol>>,
194 ) -> GblResult<UsbTransport<'a, 'b>> {
195     let protocol = android_boot_protocol.ok_or(EfiAppError::Unsupported)?;
196     match protocol.fastboot_usb_interface_stop() {
197         Err(e) if !e.is_efi_err(EFI_STATUS_NOT_STARTED) => return Err(e.into()),
198         _ => {}
199     };
200     Ok(UsbTransport::new(protocol.fastboot_usb_interface_start()?, protocol))
201 }
202 
203 /// Runs Fastboot.
run_fastboot( efi_entry: &EfiEntry, android_boot_protocol: Option<&Protocol<'_, AndroidBootProtocol>>, ) -> GblResult<()>204 pub fn run_fastboot(
205     efi_entry: &EfiEntry,
206     android_boot_protocol: Option<&Protocol<'_, AndroidBootProtocol>>,
207 ) -> GblResult<()> {
208     let mut gpt_devices = find_gpt_devices(efi_entry)?;
209     let mut gbl_fb = GblFastboot::new(&mut gpt_devices);
210     // TODO(b/328786603): Figure out where to get download buffer size.
211     let mut download_buffer = vec![0u8; 512 * 1024 * 1024];
212     let mut fastboot = Fastboot::new(&mut download_buffer[..]);
213 
214     let mut usb = match init_usb(&android_boot_protocol) {
215         Ok(v) => Some(v),
216         Err(e) => {
217             efi_println!(efi_entry, "Failed to start Fastboot over USB. {:?}.", e);
218             None
219         }
220     };
221 
222     match with_efi_network(efi_entry, |socket| -> GblResult<()> {
223         fastboot_loop(efi_entry, &mut gbl_fb, &mut fastboot, Some(socket), usb.as_mut())
224     }) {
225         Err(e) => {
226             efi_println!(efi_entry, "Failed to start EFI network. {:?}.", e);
227             fastboot_loop(efi_entry, &mut gbl_fb, &mut fastboot, None, usb.as_mut())
228         }
229         v => v?,
230     }
231 }
232