• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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::{
21     net::{EfiGblNetwork, EfiTcpSocket},
22     ops::Ops,
23 };
24 use alloc::{boxed::Box, vec::Vec};
25 use core::{
26     cmp::min, fmt::Write, future::Future, mem::take, pin::Pin, sync::atomic::AtomicU64,
27     time::Duration,
28 };
29 use efi::{
30     efi_print, efi_println,
31     local_session::LocalFastbootSession,
32     protocol::{gbl_efi_fastboot_usb::GblFastbootUsbProtocol, Protocol},
33     EfiEntry,
34 };
35 use fastboot::{TcpStream, Transport};
36 use gbl_async::{block_on, YieldCounter};
37 use liberror::{Error, Result};
38 use libgbl::fastboot::{
39     run_gbl_fastboot, GblFastbootResult, GblTcpStream, GblUsbTransport, PinFutContainer,
40 };
41 
42 const DEFAULT_TIMEOUT: Duration = Duration::from_secs(5);
43 const FASTBOOT_TCP_PORT: u16 = 5554;
44 
45 pub(crate) struct EfiFastbootTcpTransport<'a, 'b, 'c> {
46     socket: &'c mut EfiTcpSocket<'a, 'b>,
47 }
48 
49 impl<'a, 'b, 'c> EfiFastbootTcpTransport<'a, 'b, 'c> {
new(socket: &'c mut EfiTcpSocket<'a, 'b>) -> Self50     fn new(socket: &'c mut EfiTcpSocket<'a, 'b>) -> Self {
51         Self { socket: socket }
52     }
53 }
54 
55 impl TcpStream for EfiFastbootTcpTransport<'_, '_, '_> {
56     /// Reads to `out` for exactly `out.len()` number bytes from the TCP connection.
read_exact(&mut self, out: &mut [u8]) -> Result<()>57     async fn read_exact(&mut self, out: &mut [u8]) -> Result<()> {
58         self.socket.receive_exact(out, DEFAULT_TIMEOUT).await
59     }
60 
61     /// Sends exactly `data.len()` number bytes from `data` to the TCP connection.
write_exact(&mut self, data: &[u8]) -> Result<()>62     async fn write_exact(&mut self, data: &[u8]) -> Result<()> {
63         self.socket.send_exact(data, DEFAULT_TIMEOUT).await
64     }
65 }
66 
67 impl GblTcpStream for EfiFastbootTcpTransport<'_, '_, '_> {
accept_new(&mut self) -> bool68     fn accept_new(&mut self) -> bool {
69         let efi_entry = self.socket.efi_entry;
70         self.socket.poll();
71         // If not listenining, start listening.
72         // If not connected but it's been `DEFAULT_TIMEOUT`, restart listening in case the remote
73         // client disconnects in the middle of TCP handshake and leaves the socket in a half open
74         // state.
75         if !self.socket.is_listening_or_handshaking()
76             || (!self.socket.check_active()
77                 && self.socket.time_since_last_listen() > DEFAULT_TIMEOUT)
78         {
79             let _ = self
80                 .socket
81                 .listen(FASTBOOT_TCP_PORT)
82                 .inspect_err(|e| efi_println!(efi_entry, "TCP listen error: {:?}", e));
83 
84             // TODO(b/368647237): Enable only in Fuchsia context.
85             self.socket.broadcast_fuchsia_fastboot_mdns();
86         } else if self.socket.check_active() {
87             self.socket.set_io_yield_threshold(1024 * 1024); // 1MB
88             let remote = self.socket.get_socket().remote_endpoint().unwrap();
89             efi_println!(efi_entry, "TCP connection from {}", remote);
90             return true;
91         }
92         false
93     }
94 }
95 
96 /// `UsbTransport` implements the `fastboot::Transport` trait using USB interfaces from
97 /// GBL_EFI_FASTBOOT_USB_PROTOCOL.
98 pub struct UsbTransport<'a> {
99     max_packet_size: usize,
100     protocol: Protocol<'a, GblFastbootUsbProtocol>,
101     io_yield_counter: YieldCounter,
102     // Buffer for prefetching an incoming packet in `wait_for_packet()`.
103     // Alternatively we can also consider adding an EFI event for packet arrive. But UEFI firmware
104     // may be more complicated.
105     prefetched: (Vec<u8>, usize),
106 }
107 
108 impl<'a> UsbTransport<'a> {
new(max_packet_size: usize, protocol: Protocol<'a, GblFastbootUsbProtocol>) -> Self109     fn new(max_packet_size: usize, protocol: Protocol<'a, GblFastbootUsbProtocol>) -> Self {
110         Self {
111             max_packet_size,
112             protocol,
113             io_yield_counter: YieldCounter::new(1024 * 1024),
114             prefetched: (vec![0u8; max_packet_size], 0),
115         }
116     }
117 
118     /// Polls and cache the next USB packet.
119     ///
120     /// Returns Ok(true) if there is a new packet. Ok(false) if there is no incoming packet. Err()
121     /// otherwise.
poll_next_packet(&mut self) -> Result<bool>122     fn poll_next_packet(&mut self) -> Result<bool> {
123         match &mut self.prefetched {
124             (pkt, len) if *len == 0 => match self.protocol.fastboot_usb_receive(pkt) {
125                 Ok(out_size) => {
126                     *len = out_size;
127                     return Ok(true);
128                 }
129                 Err(Error::NotReady) => return Ok(false),
130                 Err(e) => return Err(e),
131             },
132             _ => Ok(true),
133         }
134     }
135 }
136 
137 impl Transport for UsbTransport<'_> {
receive_packet(&mut self, out: &mut [u8]) -> Result<usize>138     async fn receive_packet(&mut self, out: &mut [u8]) -> Result<usize> {
139         let len = match &mut self.prefetched {
140             (pkt, len) if *len > 0 => {
141                 let out = out.get_mut(..*len).ok_or(Error::BufferTooSmall(Some(*len)))?;
142                 let src = pkt.get(..*len).ok_or(Error::Other(Some("Invalid USB read size")))?;
143                 out.clone_from_slice(src);
144                 take(len)
145             }
146             _ => self.protocol.receive_packet(out).await?,
147         };
148         // Forces a yield to the executor if the data received/sent reaches a certain
149         // threshold. This is to prevent the async code from holding up the CPU for too long
150         // in case IO speed is high and the executor uses cooperative scheduling.
151         self.io_yield_counter.increment(len.try_into().unwrap()).await;
152         Ok(len)
153     }
154 
send_packet(&mut self, packet: &[u8]) -> Result<()>155     async fn send_packet(&mut self, packet: &[u8]) -> Result<()> {
156         let mut curr = &packet[..];
157         while !curr.is_empty() {
158             let to_send = min(curr.len(), self.max_packet_size);
159             self.protocol.send_packet(&curr[..to_send], DEFAULT_TIMEOUT).await?;
160             // Forces a yield to the executor if the data received/sent reaches a certain
161             // threshold. This is to prevent the async code from holding up the CPU for too long
162             // in case IO speed is high and the executor uses cooperative scheduling.
163             self.io_yield_counter.increment(to_send.try_into().unwrap()).await;
164             curr = &curr[to_send..];
165         }
166         Ok(())
167     }
168 }
169 
170 impl GblUsbTransport for UsbTransport<'_> {
has_packet(&mut self) -> bool171     fn has_packet(&mut self) -> bool {
172         let efi_entry = self.protocol.efi_entry();
173         self.poll_next_packet()
174             .inspect_err(|e| efi_println!(efi_entry, "Error while polling next packet: {:?}", e))
175             .unwrap_or(false)
176     }
177 }
178 
179 /// Initializes the Fastboot USB interface and returns a `UsbTransport`.
init_usb(efi_entry: &EfiEntry) -> Result<UsbTransport>180 fn init_usb(efi_entry: &EfiEntry) -> Result<UsbTransport> {
181     let protocol =
182         efi_entry.system_table().boot_services().find_first_and_open::<GblFastbootUsbProtocol>()?;
183     match protocol.fastboot_usb_interface_stop() {
184         Err(e) if e != Error::NotStarted => Err(e),
185         _ => Ok(UsbTransport::new(protocol.fastboot_usb_interface_start()?, protocol)),
186     }
187 }
188 
189 // Wrapper of vector of pinned futures.
190 #[derive(Default)]
191 pub(crate) struct VecPinFut<'a>(Vec<Pin<Box<dyn Future<Output = ()> + 'a>>>);
192 
193 impl<'a> PinFutContainer<'a> for VecPinFut<'a> {
add_with<F: Future<Output = ()> + 'a>(&mut self, f: impl FnOnce() -> F)194     fn add_with<F: Future<Output = ()> + 'a>(&mut self, f: impl FnOnce() -> F) {
195         self.0.push(Box::pin(f()));
196     }
197 
for_each_remove_if( &mut self, mut cb: impl FnMut(&mut Pin<&mut (dyn Future<Output = ()> + 'a)>) -> bool, )198     fn for_each_remove_if(
199         &mut self,
200         mut cb: impl FnMut(&mut Pin<&mut (dyn Future<Output = ()> + 'a)>) -> bool,
201     ) {
202         for idx in (0..self.0.len()).rev() {
203             cb(&mut self.0[idx].as_mut()).then(|| self.0.swap_remove(idx));
204         }
205     }
206 }
207 
208 /// Initializes GBL EFI fastboot channels and runs a caller provided closure with them.
with_fastboot_channels( efi_entry: &EfiEntry, f: impl FnOnce(Option<LocalFastbootSession>, Option<UsbTransport>, Option<EfiFastbootTcpTransport>), )209 pub(crate) fn with_fastboot_channels(
210     efi_entry: &EfiEntry,
211     f: impl FnOnce(Option<LocalFastbootSession>, Option<UsbTransport>, Option<EfiFastbootTcpTransport>),
212 ) {
213     let local_session = LocalFastbootSession::start(efi_entry, Duration::from_millis(1))
214         .inspect(|_| efi_println!(efi_entry, "Starting local bootmenu."))
215         .inspect_err(|e| efi_println!(efi_entry, "Failed to start local bootmenu: {:?}", e))
216         .ok();
217 
218     let usb = init_usb(efi_entry)
219         .inspect(|_| efi_println!(efi_entry, "Started Fastboot over USB."))
220         .inspect_err(|e| efi_println!(efi_entry, "Failed to start Fastboot over USB. {:?}.", e))
221         .ok();
222 
223     let ts = AtomicU64::new(0);
224     let mut net: EfiGblNetwork = Default::default();
225     let mut tcp = net
226         .init(efi_entry, &ts)
227         .inspect(|v| {
228             efi_println!(efi_entry, "Started Fastboot over TCP");
229             efi_println!(efi_entry, "IP address:");
230             v.interface().ip_addrs().iter().for_each(|v| {
231                 efi_println!(efi_entry, "\t{}", v.address());
232             });
233         })
234         .inspect_err(|e| efi_println!(efi_entry, "Failed to start EFI network. {:?}.", e))
235         .ok();
236     let tcp = tcp.as_mut().map(|v| EfiFastbootTcpTransport::new(v));
237 
238     f(local_session, usb, tcp)
239 }
240 
fastboot(efi_gbl_ops: &mut Ops, bootimg_buf: &mut [u8]) -> Result<GblFastbootResult>241 pub fn fastboot(efi_gbl_ops: &mut Ops, bootimg_buf: &mut [u8]) -> Result<GblFastbootResult> {
242     let efi_entry = efi_gbl_ops.efi_entry;
243     efi_println!(efi_entry, "Entering fastboot mode...");
244 
245     let mut res = Default::default();
246     with_fastboot_channels(efi_entry, |local, usb, tcp| {
247         let download_buffers = vec![vec![0u8; 512 * 1024 * 1024]; 2].into();
248         res = block_on(run_gbl_fastboot(
249             efi_gbl_ops,
250             &download_buffers,
251             VecPinFut::default(),
252             local,
253             usb,
254             tcp,
255             bootimg_buf,
256         ));
257     });
258 
259     efi_println!(efi_entry, "Leaving fastboot mode...");
260 
261     Ok(res)
262 }
263