diff options
author | Yecheng Zhao <zyecheng@google.com> | 2024-03-12 19:55:28 +0000 |
---|---|---|
committer | Yecheng Zhao <zyecheng@google.com> | 2024-03-15 18:35:42 +0000 |
commit | ce1f4d88ed1412cecae2f84615057cfb524b0d3b (patch) | |
tree | 994fbea6251d2b64d53d2d68743b2f50c6fd7a86 | |
parent | 7b7fe1bfd57f5c508b042e2e868ee4ac3cce7208 (diff) | |
download | libbootloader-ce1f4d88ed1412cecae2f84615057cfb524b0d3b.tar.gz |
Enable Fastboot over TCP
Bug: 328786603
Change-Id: I42a3f03772377ecac7762a805c6e70b516a36989
-rw-r--r-- | gbl/efi/BUILD | 3 | ||||
-rw-r--r-- | gbl/efi/src/error.rs | 8 | ||||
-rw-r--r-- | gbl/efi/src/fastboot.rs | 132 | ||||
-rw-r--r-- | gbl/efi/src/main.rs | 4 | ||||
-rw-r--r-- | gbl/efi/src/net.rs | 467 | ||||
-rw-r--r-- | gbl/efi/src/utils.rs | 2 | ||||
-rw-r--r-- | gbl/libefi/src/protocol.rs | 14 |
7 files changed, 622 insertions, 8 deletions
diff --git a/gbl/efi/BUILD b/gbl/efi/BUILD index 3ea964b..7f20ec0 100644 --- a/gbl/efi/BUILD +++ b/gbl/efi/BUILD @@ -26,8 +26,10 @@ rust_binary( "src/android_boot.rs", "src/avb.rs", "src/error.rs", + "src/fastboot.rs", "src/fuchsia_boot.rs", "src/main.rs", + "src/net.rs", "src/riscv64.rs", "src/utils.rs", ], @@ -46,6 +48,7 @@ rust_binary( "@gbl//libbootconfig", "@gbl//libbootimg", "@gbl//libefi", + "@gbl//libfastboot", "@gbl//libfdt", "@gbl//libgbl", "@gbl//libstorage", diff --git a/gbl/efi/src/error.rs b/gbl/efi/src/error.rs index 60558b9..4d3048b 100644 --- a/gbl/efi/src/error.rs +++ b/gbl/efi/src/error.rs @@ -17,8 +17,10 @@ use boot::BootError; use bootconfig::BootConfigError; use bootimg::ImageError; use efi::EfiError; +use fastboot::TransportError; use fdt::FdtError; use gbl_storage::StorageError; +use smoltcp::socket::tcp::{ListenError, RecvError, SendError}; use zbi::ZbiError; /// Error types for EFI application. @@ -32,6 +34,8 @@ pub enum EfiAppError { NoFdt, NotFound, NoZbiImage, + PeerClosed, + Timeout, Unsupported, } @@ -114,8 +118,12 @@ composite_enum! { EfiError(EfiError), FdtError(FdtError), ImageError(ImageError), + ListenError(ListenError), + RecvError(RecvError), + SendError(SendError), SlotVerifyError(SlotVerifyError<'static>), StorageError(StorageError), + TransportError(TransportError), ZbiError(ZbiError), } } diff --git a/gbl/efi/src/fastboot.rs b/gbl/efi/src/fastboot.rs new file mode 100644 index 0000000..8789e04 --- /dev/null +++ b/gbl/efi/src/fastboot.rs @@ -0,0 +1,132 @@ +// 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::Result; +use crate::net::{with_efi_network, EfiTcpSocket}; +use core::fmt::Write; +use core::str::Split; +use efi::{efi_print, efi_println, EfiEntry}; +use fastboot::{CommandError, Fastboot, FastbootImplementation, TcpStream, TransportError}; + +const DEFAULT_TIMEOUT_MS: u64 = 5_000; +const FASTBOOT_TCP_PORT: u16 = 5554; + +struct EfiFastbootTcpTransport<'a, 'b, 'c> { + transport_error: &'c mut Result<()>, + socket: &'c mut EfiTcpSocket<'a, 'b>, +} + +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]) -> core::result::Result<(), TransportError> { + *self.transport_error = self.socket.receive_exact(out, DEFAULT_TIMEOUT_MS); + self.transport_error.as_ref().map_err(|_| TransportError::Others(""))?; + Ok(()) + } + + /// Sends exactly `data.len()` number bytes from `data` to the TCP connection. + fn write_exact(&mut self, data: &[u8]) -> core::result::Result<(), TransportError> { + *self.transport_error = self.socket.send_exact(data, DEFAULT_TIMEOUT_MS); + self.transport_error.as_ref().map_err(|_| TransportError::Others(""))?; + Ok(()) + } +} + +/// TODO(b/328786603): Placeholder only. It'll be replaced by a generic GBL Fastboot implementation +/// in a separate library. +pub struct GblFastboot {} + +impl FastbootImplementation for GblFastboot { + fn get_var( + &mut self, + _: &str, + _: Split<char>, + _: &mut [u8], + ) -> core::result::Result<usize, CommandError> { + Err("Not found".into()) + } + + fn get_var_all<F>(&mut self, _: F) -> core::result::Result<(), CommandError> + where + F: FnMut(&str, &[&str], &str), + { + Ok(()) + } +} + +/// Internal helper for performing Fastboot over TCP. +fn fastboot_tcp_usb( + socket: &mut EfiTcpSocket, + efi_entry: &EfiEntry, + download_buffer: &mut [u8], +) -> Result<()> { + let mut gbl_fastboot = GblFastboot {}; + efi_println!(efi_entry, "Listening for Fastboot over TCP..."); + efi_println!(efi_entry, "IP addresses:"); + socket.interface().ip_addrs().iter().for_each(|v| { + efi_println!(efi_entry, "\t{}", v.address()); + }); + loop { + socket.listen(FASTBOOT_TCP_PORT)?; + let listen_start = EfiTcpSocket::timestamp(0); + loop { + socket.poll(); + if socket.check_active() { + // Has connection. + efi_println!( + efi_entry, + "Connection from {}", + socket.get_socket().remote_endpoint().unwrap() + ); + + let mut transport_error = Ok(()); + let mut transport = EfiFastbootTcpTransport { + transport_error: &mut transport_error, + socket: socket, + }; + let mut fastboot = Fastboot::new(&mut download_buffer[..]); + let _ = fastboot.run_tcp_session(&mut transport, &mut gbl_fastboot); + efi_println!(efi_entry, "Fastboot TCP session ends. {:?}", transport_error); + break; + } else if EfiTcpSocket::timestamp(listen_start) > 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. + break; + } + + // Perform Fastboot over USB here. + } + } +} + +/// Runs Fastboot. +pub fn run_fastboot(efi_entry: &EfiEntry) -> Result<()> { + // TODO(b/328786603): Figure out where to get download buffer size. + let mut download_buffer = vec![0u8; 512 * 1024 * 1024]; + match with_efi_network(efi_entry, |socket| -> Result<()> { + fastboot_tcp_usb(socket, efi_entry, &mut download_buffer[..]) + })? { + Err(e) => { + efi_println!(efi_entry, "Failed to initilaize network {:?}", e); + // Perform Fastboot over USB here. + } + _ => {} + }; + Ok(()) +} diff --git a/gbl/efi/src/main.rs b/gbl/efi/src/main.rs index 92e2e29..aad07fb 100644 --- a/gbl/efi/src/main.rs +++ b/gbl/efi/src/main.rs @@ -39,7 +39,9 @@ mod riscv64; mod android_boot; mod avb; mod error; +mod fastboot; mod fuchsia_boot; +mod net; fn main(image_handle: *mut core::ffi::c_void, systab_ptr: *mut EfiSystemTable) -> Result<()> { // SAFETY: Called only once here upon EFI app entry. @@ -54,7 +56,7 @@ fn main(image_handle: *mut core::ffi::c_void, systab_ptr: *mut EfiSystemTable) - match wait_key_stroke(&entry, 'f', 2000) { Ok(true) => { efi_println!(entry, "'f' pressed."); - // TODO(b/328786603): Implement fastboot. + fastboot::run_fastboot(&entry)?; } _ => {} } diff --git a/gbl/efi/src/net.rs b/gbl/efi/src/net.rs new file mode 100644 index 0000000..0c994f6 --- /dev/null +++ b/gbl/efi/src/net.rs @@ -0,0 +1,467 @@ +// 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. + +use alloc::boxed::Box; +use alloc::vec::Vec; +use core::fmt::Write; +use core::sync::atomic::{AtomicU64, Ordering}; +use efi::defs::{ + EfiEvent, EfiMacAddress, EFI_STATUS_ALREADY_STARTED, EFI_STATUS_NOT_STARTED, + EFI_TIMER_DELAY_TIMER_PERIODIC, +}; +use efi::{ + efi_print, efi_println, DeviceHandle, EfiEntry, EventNotify, EventType, Protocol, + SimpleNetworkProtocol, Tpl, +}; +//use fastboot::{Fastboot, FastbootImplementation, FormattedBytes, TcpStream, TransportError}; +use crate::error::{EfiAppError, Result}; +use crate::utils::{get_device_path, loop_with_timeout, ms_to_100ns}; +use smoltcp::iface::{Config, Interface, SocketSet}; +use smoltcp::phy; +use smoltcp::phy::{Device, DeviceCapabilities, Medium}; +use smoltcp::socket::tcp; +use smoltcp::time::Instant; +use smoltcp::wire::{EthernetAddress, IpAddress, IpCidr, Ipv6Address}; + +/// Maintains a timestamp needed by smoltcp network. It's updated periodically during timer event. +static NETWORK_TIMESTAMP: AtomicU64 = AtomicU64::new(0); +/// Ethernet frame size for frame pool. +const ETHERNET_FRAME_SIZE: usize = 1536; +// Update period in milliseconds for `NETWORK_TIMESTAMP`. +const NETWORK_TIMESTAMP_UPDATE_PERIOD: u64 = 50; +// Size of the socket tx/rx application data buffer. +const SOCKET_TX_RX_BUFFER: usize = 64 * 1024; + +/// Performs a shutdown and restart of the simple network protocol. +fn reset_simple_network<'a>(snp: &Protocol<'a, SimpleNetworkProtocol>) -> Result<()> { + match snp.shutdown() { + Err(e) if !e.is_efi_err(EFI_STATUS_NOT_STARTED) => return Err(e.into()), + _ => {} + }; + + match snp.start() { + Err(e) if !e.is_efi_err(EFI_STATUS_ALREADY_STARTED) => { + return Err(e.into()); + } + _ => {} + }; + snp.initialize(0, 0).unwrap(); + Ok(snp.reset(true)?) +} + +/// `EfiNetworkDevice` manages a frame pool and handles receiving/sending network frames. +pub struct EfiNetworkDevice<'a> { + protocol: Protocol<'a, SimpleNetworkProtocol>, + rx_frame: Box<[u8; ETHERNET_FRAME_SIZE]>, + tx_frames: Vec<*mut [u8; ETHERNET_FRAME_SIZE]>, + tx_frame_curr: usize, // Circular next index into tx_frames. + efi_entry: &'a EfiEntry, +} + +impl<'a> EfiNetworkDevice<'a> { + /// Creates an new instance. Allocates `extra_tx_frames+1` number of TX frames. + pub fn new( + protocol: Protocol<'a, SimpleNetworkProtocol>, + extra_tx_frames: usize, + efi_entry: &'a EfiEntry, + ) -> Self { + let mut ret = Self { + protocol: protocol, + rx_frame: Box::new([0u8; ETHERNET_FRAME_SIZE]), + tx_frames: vec![core::ptr::null_mut(); extra_tx_frames + 1], + tx_frame_curr: 0, + efi_entry: efi_entry, + }; + ret.tx_frames + .iter_mut() + .for_each(|v| *v = Box::into_raw(Box::new([0u8; ETHERNET_FRAME_SIZE]))); + ret + } +} + +impl Drop for EfiNetworkDevice<'_> { + fn drop(&mut self) { + if let Err(e) = self.protocol.shutdown() { + if !e.is_efi_err(EFI_STATUS_NOT_STARTED) { + // If shutdown fails, the protocol might still be operating on transmit buffers, + // which can cause undefined behavior. Thus we need to panic. + panic!("Failed to shutdown EFI network. {:?}", e); + } + } + + // Deallocate TX frames. + self.tx_frames.iter_mut().for_each(|v| { + // SAFETY: + // Each pointer is created by `Box::new()` in `EfiNetworkDevice::new()`. Thus the + // pointer is valid and layout matches. + let _ = unsafe { Box::from_raw(v) }; + }); + } +} + +// Implements network device trait backend for the `smoltcp` crate. +impl<'a> Device for EfiNetworkDevice<'a> { + type RxToken<'b> = RxToken<'b> where Self: 'b; + type TxToken<'b> = TxToken<'a, 'b> where Self: 'b; + + fn capabilities(&self) -> DeviceCapabilities { + // Taken from upstream example. + let mut res: DeviceCapabilities = Default::default(); + res.max_transmission_unit = 65535; + res.medium = Medium::Ethernet; + res + } + + fn receive(&mut self, _: Instant) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)> { + let mut recv_size = self.rx_frame.len(); + // Receive the next packet from the device. + self.protocol + .receive(None, Some(&mut recv_size), &mut self.rx_frame[..], None, None, None) + .ok()?; + match recv_size > 0 { + true => Some(( + RxToken(&mut self.rx_frame[..recv_size]), + TxToken { + protocol: &self.protocol, + tx_frames: &mut self.tx_frames[..], + curr: &mut self.tx_frame_curr, + efi_entry: self.efi_entry, + }, + )), + _ => None, + } + } + + fn transmit(&mut self, _: Instant) -> Option<Self::TxToken<'_>> { + Some(TxToken { + protocol: &self.protocol, + tx_frames: &mut self.tx_frames[..], + curr: &mut self.tx_frame_curr, + efi_entry: self.efi_entry, + }) + } +} + +/// In smoltcp, a `RxToken` is used to receive/process a frame when consumed. +pub struct RxToken<'a>(&'a mut [u8]); + +impl phy::RxToken for RxToken<'_> { + fn consume<R, F>(self, f: F) -> R + where + F: FnOnce(&mut [u8]) -> R, + { + f(self.0) + } +} + +/// In smoltcp, a `TxToken` is used to transmit a frame when consumed. +pub struct TxToken<'a: 'b, 'b> { + tx_frames: &'b mut [*mut [u8; ETHERNET_FRAME_SIZE]], + curr: &'b mut usize, + protocol: &'b Protocol<'a, SimpleNetworkProtocol>, + efi_entry: &'b EfiEntry, +} + +impl TxToken<'_, '_> { + /// Tries to allocate a send buffer. + fn try_get_buffer(&mut self) -> Option<*mut [u8; ETHERNET_FRAME_SIZE]> { + let mut ptr: *mut core::ffi::c_void = core::ptr::null_mut(); + let mut interrupt_status = 0u32; + // Recyle a buffer or take one from `tx_frames`. + match self.protocol.get_status(Some(&mut interrupt_status), Some(&mut ptr)) { + Ok(()) if self.tx_frames.contains(&(ptr as *mut _)) => Some(ptr as *mut _), + _ if *self.curr < self.tx_frames.len() => { + // If we can't recycle a buffer, see if we can take one from the pool. + let res = *self.curr; + *self.curr = *self.curr + 1; + Some(self.tx_frames[res]) + } + _ => None, + } + } +} + +impl phy::TxToken for TxToken<'_, '_> { + fn consume<R, F>(mut self, len: usize, f: F) -> R + where + F: FnOnce(&mut [u8]) -> R, + { + loop { + match loop_with_timeout(self.efi_entry, 5000, || self.try_get_buffer().ok_or(false)) { + Ok(Some(send_buffer)) => { + // SAFETY: + // * The pointer is confirmed to come from one of `self.tx_frames`. It's + // created via `Box::new()` in `EfiNetworkDevice::new()`. Thus it is properly + // aligned, dereferenceable and initialized. + // * The pointer is either recycled from `self.protocol.get_status` or newly + // allocated from `self.tx_frames`. Thus There's no other references to it. + // * The reference is only used for passing to `f` and goes out of scope + // immediately after. + let result = f(&mut unsafe { send_buffer.as_mut() }.unwrap()[..len]); + + // SAFETY: + // * `send_buffer` comes from `EfiNetworkDevice::tx_frames`. It has a valid + // length at least `len`. `EfiNetworkDevice` shuts down network on drop. Thus + // the transmit buffer remains valid throughout the operation of the network + // protocol. + // * `send_buffer` is either recycled from `self.protocol.get_status()` or newly + // allocated from `self.tx_frames`. There's no other references to it. + // * `self.curr` stricly increases for each new allocation until + // `reset_simple_network()`. Thus there'll be no other references to the buffer + // until it is either recycled or `reset_simple_network()` is called. + let _ = unsafe { + self.protocol.transmit( + 0, + send_buffer.as_mut().unwrap().get_mut(..len).unwrap(), + Default::default(), // Src mac address don't care + Default::default(), // Dest mac address don't care + 0, + ) + }; + + return result; + } + Ok(None) => { + // Some UEFI firmware has internal network service that also recycle buffers, + // in which case our buffer may be hijacked and will never be returned from our + // call. If we run into this case, shutdown and restart the network and try + // again. Shutting down network releases all pending send/receive buffers + // internally retained. + efi_println!( + self.efi_entry, + "Timeout recycling TX buffers. Resetting network." + ); + // Panics if this fails, as we have effectively lost control over network's + // used of buffers. + reset_simple_network(self.protocol).unwrap(); + *self.curr = 0; + } + _ => {} // `loop_with_timeout` failure. Try again. + }; + } + } +} + +/// Returns the current value of timestamp. +fn timestamp() -> u64 { + NETWORK_TIMESTAMP.load(Ordering::Relaxed) +} + +/// Returns a smoltcp time `Instant` value. +fn time_instant() -> Instant { + Instant::from_millis(i64::try_from(timestamp()).unwrap()) +} + +/// Find the first available network device. +fn find_net_device(efi_entry: &EfiEntry) -> Result<DeviceHandle> { + // Find the device whose path is the "smallest" lexicographically, this ensures that it's not + // any child network device of some other node. e1000 tends to add a child network device for + // ipv4 and ipv6 configuration information. + efi_entry + .system_table() + .boot_services() + .locate_handle_buffer_by_protocol::<SimpleNetworkProtocol>()? + .handles() + .iter() + .map(|handle| (*handle, get_device_path(efi_entry, *handle))) + // Ignore devices that fail to get device path. + .filter_map(|(handle, path)| path.ok().map(|v| (handle, v))) + // Ignore devices that have NULL path. + .filter_map(|(handle, path)| path.text().is_some().then(|| (handle, path))) + // Finds the minimum path lexicographically. + .min_by(|lhs, rhs| Ord::cmp(lhs.1.text().unwrap(), rhs.1.text().unwrap())) + .map(|(h, _)| h) + .ok_or(EfiAppError::NotFound.into()) +} + +/// Derives a link local ethernet mac address and IPv6 address from `EfiMacAddress`. +fn ll_mac_ip6_addr_from_efi_mac(mac: EfiMacAddress) -> (EthernetAddress, IpAddress) { + let ll_mac_bytes = &mac.addr[..6]; + let mut ip6_bytes = [0u8; 16]; + ip6_bytes[0] = 0xfe; + ip6_bytes[1] = 0x80; + ip6_bytes[8] = ll_mac_bytes[0] ^ 2; + ip6_bytes[9] = ll_mac_bytes[1]; + ip6_bytes[10] = ll_mac_bytes[2]; + ip6_bytes[11] = 0xff; + ip6_bytes[12] = 0xfe; + ip6_bytes[13] = ll_mac_bytes[3]; + ip6_bytes[14] = ll_mac_bytes[4]; + ip6_bytes[15] = ll_mac_bytes[5]; + + ( + EthernetAddress::from_bytes(ll_mac_bytes), + IpAddress::Ipv6(Ipv6Address::from_bytes(&ip6_bytes[..])), + ) +} + +/// `EfiTcpSocket` groups together necessary components for performing TCP. +pub struct EfiTcpSocket<'a, 'b> { + efi_net_dev: &'b mut EfiNetworkDevice<'a>, + interface: &'b mut Interface, + sockets: &'b mut SocketSet<'b>, + efi_entry: &'a EfiEntry, +} + +impl<'a, 'b> EfiTcpSocket<'a, 'b> { + /// Resets the socket and starts listening for new TCP connection. + pub fn listen(&mut self, port: u16) -> Result<()> { + self.get_socket().abort(); + self.get_socket().listen(port)?; + Ok(()) + } + + /// Polls network device. + pub fn poll(&mut self) { + self.interface.poll(time_instant(), self.efi_net_dev, self.sockets); + } + + /// Polls network and check if the socket is in an active state. + pub fn check_active(&mut self) -> bool { + self.poll(); + self.get_socket().is_active() + } + + /// Gets a reference to the smoltcp socket object. + pub fn get_socket(&mut self) -> &mut tcp::Socket<'b> { + // We only consider single socket use case for now. + let handle = self.sockets.iter().next().unwrap().0; + self.sockets.get_mut::<tcp::Socket>(handle) + } + + /// Checks whether a socket is closed. + fn is_closed(&mut self) -> bool { + return !self.get_socket().is_open() || self.get_socket().state() == tcp::State::CloseWait; + } + + /// Receives exactly `out.len()` number of bytes to `out`. + pub fn receive_exact(&mut self, out: &mut [u8], timeout: u64) -> Result<()> { + let mut recv_size = 0; + loop_with_timeout(self.efi_entry, timeout, || -> core::result::Result<Result<()>, bool> { + self.poll(); + if self.is_closed() { + return Ok(Err(EfiAppError::PeerClosed.into())); + } else if self.get_socket().can_recv() { + let this_recv = match self.get_socket().recv_slice(&mut out[recv_size..]) { + Err(e) => return Ok(Err(e.into())), + Ok(v) => v, + }; + recv_size += this_recv; + if recv_size == out.len() { + return Ok(Ok(())); + } + + return Err(this_recv > 0); + } + Err(false) + })? + .ok_or(EfiAppError::Timeout)? + } + + /// Sends exactly `data.len()` number of bytes from `data`. + pub fn send_exact(&mut self, data: &[u8], timeout: u64) -> Result<()> { + let mut sent_size = 0; + let mut last_send_queue = 0usize; + loop_with_timeout(self.efi_entry, timeout, || -> core::result::Result<Result<()>, bool> { + self.poll(); + if sent_size == data.len() && self.get_socket().send_queue() == 0 { + return Ok(Ok(())); + } else if self.is_closed() { + return Ok(Err(EfiAppError::PeerClosed.into())); + } + // As long as some data is sent, reset the timeout. + let reset = self.get_socket().send_queue() != last_send_queue; + if self.get_socket().can_send() && sent_size < data.len() { + sent_size += match self.get_socket().send_slice(&data[sent_size..]) { + Err(e) => return Ok(Err(e.into())), + Ok(v) => v, + }; + } + last_send_queue = self.get_socket().send_queue(); + Err(reset) + })? + .ok_or(EfiAppError::Timeout)? + } + + /// Gets the smoltcp `Interface` for this socket. + pub fn interface(&self) -> &Interface { + self.interface + } + + /// Returns the number of milliseconds elapsed since the `base` timestamp. + pub fn timestamp(base: u64) -> u64 { + let curr = timestamp(); + // Assume there can be at most one overflow. + match curr < base { + true => u64::MAX - (base - curr), + false => curr - base, + } + } +} + +/// Initializes network environment and provides a TCP socket for callers to run a closure. The API +/// handles clean up automatically after returning. +pub fn with_efi_network<F, R>(efi_entry: &EfiEntry, mut f: F) -> Result<R> +where + F: FnMut(&mut EfiTcpSocket) -> R, +{ + let bs = efi_entry.system_table().boot_services(); + + // Creates timestamp update event. + let _ = NETWORK_TIMESTAMP.swap(0, Ordering::Relaxed); + let mut notify_fn = |_: EfiEvent| { + NETWORK_TIMESTAMP.fetch_add(NETWORK_TIMESTAMP_UPDATE_PERIOD, Ordering::Relaxed); + }; + let mut notify = EventNotify::new(Tpl::Callback, &mut notify_fn); + let timer = bs.create_event(EventType::TimerNotifySignal, Some(&mut notify))?; + bs.set_timer( + &timer, + EFI_TIMER_DELAY_TIMER_PERIODIC, + ms_to_100ns(NETWORK_TIMESTAMP_UPDATE_PERIOD)?, + )?; + + // Creates and initializes simple network protocol. + let snp_dev = find_net_device(efi_entry).unwrap(); + let snp = bs.open_protocol::<SimpleNetworkProtocol>(snp_dev).unwrap(); + reset_simple_network(&snp)?; + + // Gets our MAC address and IPv6 address. + // We can also consider getting this from vendor configuration. + let (ll_mac, ll_ip6_addr) = ll_mac_ip6_addr_from_efi_mac(snp.mode()?.current_address); + + // Creates an `EfiNetworkDevice`. + // Allocates 7(chosen randomly) extra TX frames. Revisits if it is not enough. + let mut efi_net_dev = EfiNetworkDevice::new(snp, 7, &efi_entry); + // Configures smoltcp network interface. + let mut interface = + Interface::new(Config::new(ll_mac.into()), &mut efi_net_dev, time_instant()); + interface.update_ip_addrs(|ip_addrs| ip_addrs.push(IpCidr::new(ll_ip6_addr, 64)).unwrap()); + // Creates an instance of socket. + let mut tx_buffer = vec![0u8; SOCKET_TX_RX_BUFFER]; + let mut rx_buffer = vec![0u8; SOCKET_TX_RX_BUFFER]; + let tx_socket_buffer = tcp::SocketBuffer::new(&mut tx_buffer[..]); + let rx_socket_buffer = tcp::SocketBuffer::new(&mut rx_buffer[..]); + let socket = tcp::Socket::new(rx_socket_buffer, tx_socket_buffer); + let mut sockets: [_; 1] = Default::default(); + let mut sockets = SocketSet::new(&mut sockets[..]); + let _ = sockets.add(socket); + let mut socket = EfiTcpSocket { + efi_net_dev: &mut efi_net_dev, + interface: &mut interface, + sockets: &mut sockets, + efi_entry: efi_entry, + }; + + Ok(f(&mut socket)) +} diff --git a/gbl/efi/src/utils.rs b/gbl/efi/src/utils.rs index c02b7bb..d74989e 100644 --- a/gbl/efi/src/utils.rs +++ b/gbl/efi/src/utils.rs @@ -184,7 +184,7 @@ pub fn cstr_bytes_to_str(data: &[u8]) -> Result<&str> { } /// Converts 1 ms to number of 100 nano seconds -fn ms_to_100ns(ms: u64) -> Result<u64> { +pub fn ms_to_100ns(ms: u64) -> Result<u64> { Ok(ms.checked_mul(1000 * 10).ok_or(EfiAppError::ArithmeticOverflow)?) } diff --git a/gbl/libefi/src/protocol.rs b/gbl/libefi/src/protocol.rs index d8961be..ef7c31a 100644 --- a/gbl/libefi/src/protocol.rs +++ b/gbl/libefi/src/protocol.rs @@ -486,19 +486,21 @@ impl<'a> Protocol<'a, SimpleNetworkProtocol> { /// /// # Safety /// - /// Caller must ensure that `buf` remains valid until either 1) the buffer address re-appears - /// in `recycled_buffer` from `Self::get_status()`, or 2) Self::Shutdown() is called and - /// returns either Ok(()) or EFI_STATUS_NOT_STARTED. Otherwise, the driver may still have - /// modifiable access to the buffer and causes undefined behavior if the buffer goes out of - /// scope earlier. + /// * `buf` needs to be a valid buffer. + /// * There should not be any existing references to memory pointed by `buf`. + /// * Because `buf` is internally retained by the network. `buf` should remain valid and not + /// dereferenced until either 1) the buffer address re-appears in `recycled_buffer` from + /// `Self::get_status()` or 2) Self::Shutdown() is called and returns either Ok(()) or + /// EFI_STATUS_NOT_STARTED. pub unsafe fn transmit( &self, header_size: usize, - buf: &mut [u8], + buf: *mut [u8], mut src: EfiMacAddress, mut dest: EfiMacAddress, mut protocol: u16, ) -> EfiResult<()> { + let buf = buf.as_mut().unwrap(); // SAFETY: // See safety reasoning of `start()`. // All pointers passed are valid, outlive the call and are not retained by the call. |