| // 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 crate::{ |
| error::{listen_to_unified, recv_to_unified, send_to_unified}, |
| utils::{get_device_path, loop_with_timeout}, |
| }; |
| use alloc::{boxed::Box, vec::Vec}; |
| use core::{ |
| fmt::Write, |
| sync::atomic::{AtomicU64, Ordering}, |
| time::Duration, |
| }; |
| use efi::{ |
| efi_print, efi_println, |
| protocol::{simple_network::SimpleNetworkProtocol, Protocol}, |
| utils::Timeout, |
| DeviceHandle, EfiEntry, Event, EventNotify, EventType, Tpl, |
| }; |
| use efi_types::{EfiEvent, EfiMacAddress, EFI_TIMER_DELAY_TIMER_PERIODIC}; |
| use gbl_async::{yield_now, YieldCounter}; |
| use liberror::{Error, Result}; |
| use libgbl::fastboot::fuchsia_fastboot_mdns_packet; |
| use smoltcp::{ |
| iface::{Config, Interface, SocketSet, SocketStorage}, |
| phy, |
| phy::{Device, DeviceCapabilities, Medium}, |
| socket::{ |
| tcp::{Socket as TcpSocket, SocketBuffer, State}, |
| udp::{PacketBuffer, Socket as UdpSocket, UdpMetadata}, |
| }, |
| storage::PacketMetadata, |
| time::Instant, |
| wire::{EthernetAddress, IpAddress, IpCidr, IpListenEndpoint, Ipv6Address}, |
| }; |
| |
| /// Ethernet frame size for frame pool. |
| const ETHERNET_FRAME_SIZE: usize = 1536; |
| // Update period for `NETWORK_TIMESTAMP`. |
| const NETWORK_TIMESTAMP_UPDATE_PERIOD: Duration = Duration::from_millis(50); |
| // Size of the socket tx/rx application data buffer. |
| const SOCKET_TX_RX_BUFFER: usize = 256 * 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 != Error::NotStarted => return Err(e), |
| _ => {} |
| }; |
| |
| match snp.start() { |
| Err(e) if e != Error::AlreadyStarted => return Err(e), |
| _ => {} |
| }; |
| snp.initialize(0, 0)?; |
| 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 != Error::NotStarted { |
| // 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. |
| drop(unsafe { Box::<[u8; ETHERNET_FRAME_SIZE]>::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, Duration::from_secs(5), || { |
| 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. |
| }; |
| } |
| } |
| } |
| |
| /// 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(Error::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> { |
| pub(crate) efi_entry: &'a EfiEntry, |
| efi_net_dev: &'b mut EfiNetworkDevice<'a>, |
| interface: Interface, |
| socket_set: SocketSet<'b>, |
| io_yield_counter: YieldCounter, |
| last_listen_timestamp: Option<u64>, |
| _time_update_event: Event<'a, 'b>, |
| timestamp: &'b AtomicU64, |
| fuchsia_fastboot_mdns_packet: Vec<u8>, |
| } |
| |
| 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).map_err(listen_to_unified)?; |
| self.last_listen_timestamp = Some(self.timestamp(0).as_millis() as u64); |
| Ok(()) |
| } |
| |
| // Checks if the socket is listening or performing handshake. |
| pub fn is_listening_or_handshaking(&mut self) -> bool { |
| matches!(self.get_socket().state(), State::Listen | State::SynReceived) |
| } |
| |
| /// Returns the amount of time elapsed since last call to `Self::listen()`. If `listen()` has |
| /// never been called, `Duration::MAX` is returned. |
| pub fn time_since_last_listen(&mut self) -> Duration { |
| self.last_listen_timestamp.map(|v| self.timestamp(v)).unwrap_or(Duration::MAX) |
| } |
| |
| /// Polls network device. |
| pub fn poll(&mut self) { |
| self.interface.poll(self.instant(), self.efi_net_dev, &mut self.socket_set); |
| } |
| |
| /// 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 TcpSocket<'b> { |
| // We only consider single socket use case for now. |
| let handle = self.socket_set.iter().next().unwrap().0; |
| self.socket_set.get_mut::<TcpSocket>(handle) |
| } |
| |
| /// Checks whether a socket is closed. |
| fn is_closed(&mut self) -> bool { |
| return !self.get_socket().is_open() || self.get_socket().state() == State::CloseWait; |
| } |
| |
| /// Sets the maximum number of bytes to read or write before a force await. |
| pub fn set_io_yield_threshold(&mut self, threshold: u64) { |
| self.io_yield_counter = YieldCounter::new(threshold) |
| } |
| |
| /// Receives exactly `out.len()` number of bytes to `out`. |
| pub async fn receive_exact(&mut self, out: &mut [u8], timeout: Duration) -> Result<()> { |
| let timer = Timeout::new(self.efi_entry, timeout)?; |
| let mut curr = &mut out[..]; |
| while !curr.is_empty() { |
| self.poll(); |
| let mut has_progress = false; |
| |
| if self.is_closed() { |
| return Err(Error::Disconnected); |
| } else if timer.check()? { |
| return Err(Error::Timeout); |
| } else if self.get_socket().can_recv() { |
| let recv_size = self.get_socket().recv_slice(curr).map_err(recv_to_unified)?; |
| curr = curr.get_mut(recv_size..).ok_or(Error::BadIndex(recv_size))?; |
| has_progress = recv_size > 0; |
| // Forces a yield to the executor if the data received/sent reaches a certain |
| // threshold. This is to prevent the async code from holding up the CPU for too long |
| // in case IO speed is high and the executor uses cooperative scheduling. |
| self.io_yield_counter.increment(recv_size.try_into().unwrap()).await; |
| } |
| |
| match has_progress { |
| true => timer.reset(timeout)?, |
| _ => yield_now().await, |
| } |
| } |
| Ok(()) |
| } |
| |
| /// Sends exactly `data.len()` number of bytes from `data`. |
| pub async fn send_exact(&mut self, data: &[u8], timeout: Duration) -> Result<()> { |
| let timer = Timeout::new(self.efi_entry, timeout)?; |
| let mut curr = &data[..]; |
| let mut last_send_queue = self.get_socket().send_queue(); |
| loop { |
| self.poll(); |
| if curr.is_empty() && self.get_socket().send_queue() == 0 { |
| return Ok(()); |
| } else if self.is_closed() { |
| return Err(Error::Disconnected.into()); |
| } else if timer.check()? { |
| return Err(Error::Timeout.into()); |
| } |
| |
| let mut has_progress = false; |
| // Checks if any data in the queue is sent. |
| if self.get_socket().send_queue() != last_send_queue { |
| last_send_queue = self.get_socket().send_queue(); |
| has_progress = true; |
| } |
| // Checks if there are more data to be queued. |
| if self.get_socket().can_send() && !curr.is_empty() { |
| let sent = self.get_socket().send_slice(curr).map_err(send_to_unified)?; |
| curr = curr.get(sent..).ok_or(Error::BadIndex(sent))?; |
| // Forces a yield to the executor if the data received/sent reaches a certain |
| // threshold. This is to prevent the async code from holding up the CPU for too long |
| // in case IO speed is high and the executor uses cooperative scheduling. |
| self.io_yield_counter.increment(sent.try_into().unwrap()).await; |
| has_progress |= sent > 0; |
| } |
| |
| match has_progress { |
| true => timer.reset(timeout)?, |
| _ => yield_now().await, |
| } |
| } |
| } |
| |
| /// Gets the smoltcp `Interface` for this socket. |
| pub fn interface(&self) -> &Interface { |
| &self.interface |
| } |
| |
| /// Returns the duration elapsed since the `base` timestamp. |
| pub fn timestamp(&self, base_in_millis: u64) -> Duration { |
| let curr = self.timestamp.load(Ordering::Relaxed); |
| // Assume there can be at most one overflow. |
| Duration::from_millis(match curr < base_in_millis { |
| true => u64::MAX - (base_in_millis - curr), |
| false => curr - base_in_millis, |
| }) |
| } |
| |
| /// Returns a smoltcp time `Instant` value. |
| fn instant(&self) -> Instant { |
| to_smoltcp_instant(self.timestamp(0).as_millis() as u64) |
| } |
| |
| /// Broadcasts Fuchsia Fastboot MDNS service once. |
| pub fn broadcast_fuchsia_fastboot_mdns(&mut self) { |
| const MDNS_PORT: u16 = 5353; |
| const IP6_BROADCAST_ADDR: &[u8] = |
| &[0xFF, 0x02, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xFB]; |
| let ip6_broadcast = Ipv6Address::from_bytes(&IP6_BROADCAST_ADDR[..]); |
| let meta = |
| UdpMetadata { endpoint: (ip6_broadcast, MDNS_PORT).into(), meta: Default::default() }; |
| let handle = self.socket_set.iter().nth(1).unwrap().0; |
| let socket = self.socket_set.get_mut::<UdpSocket>(handle); |
| if !socket.is_open() { |
| match socket.bind(IpListenEndpoint { addr: None, port: MDNS_PORT }) { |
| Err(e) => efi_println!(self.efi_entry, "bind error: {:?}", e), |
| _ => {} |
| } |
| } |
| if socket.can_send() { |
| match socket.send_slice(&self.fuchsia_fastboot_mdns_packet, meta) { |
| Err(e) => efi_println!(self.efi_entry, "UDP send error: {:?}", e), |
| _ => {} |
| } |
| } |
| } |
| } |
| |
| /// Returns a smoltcp time `Instant` value from a u64 timestamp. |
| fn to_smoltcp_instant(ts: u64) -> Instant { |
| Instant::from_millis(i64::try_from(ts).unwrap()) |
| } |
| |
| /// Internal type that contains net driver interfaces and buffers for creating GBL network and |
| /// sockets. |
| /// |
| /// # Lifetimes |
| /// |
| /// * `'a`: Lifetime of [EfiEntry] borrowed. |
| /// * `'b`: Lifetime of [SocketStorage<'b>], which eventually refers to Self. |
| /// * `'c`: Lifetime of [AtomicU64] borrowed. |
| struct EfiGblNetworkInternal<'a, 'b, 'c> { |
| efi_entry: &'a EfiEntry, |
| tcp_tx_buffer: Vec<u8>, |
| tcp_rx_buffer: Vec<u8>, |
| udp_tx_payload_buffer: Vec<u8>, |
| udp_rx_payload_buffer: Vec<u8>, |
| udp_tx_metadata_buffer: Vec<PacketMetadata<UdpMetadata>>, |
| udp_rx_metadata_buffer: Vec<PacketMetadata<UdpMetadata>>, |
| socket_storage: [SocketStorage<'b>; 2], |
| efi_net_dev: EfiNetworkDevice<'a>, |
| timestamp: &'c AtomicU64, |
| notify_fn: Option<Box<dyn FnMut(EfiEvent) + Sync + 'c>>, |
| notify: Option<EventNotify<'b>>, |
| } |
| |
| impl<'a, 'b, 'c> EfiGblNetworkInternal<'a, 'b, 'c> { |
| /// Creates a new instance of [EfiGblNetworkInternal]. |
| fn new(efi_entry: &'a EfiEntry, timestamp: &'c AtomicU64) -> Result<Self> { |
| // Creates and initializes simple network protocol. |
| let snp_dev = find_net_device(efi_entry)?; |
| let snp = efi_entry |
| .system_table() |
| .boot_services() |
| .open_protocol::<SimpleNetworkProtocol>(snp_dev)?; |
| reset_simple_network(&snp)?; |
| |
| // The TCP stack requires ICMP6 solicitation for discovery. Enable promiscuous mode so that |
| // all uni/multicast packets can be captured. |
| match snp.set_promiscuous_mode() { |
| Err(e) => efi_println!( |
| efi_entry, |
| "Warning: Failed to set promiscuous mode {e:?}. Device may be undiscoverable", |
| ), |
| _ => {} |
| } |
| |
| Ok(Self { |
| efi_entry, |
| tcp_tx_buffer: vec![0u8; SOCKET_TX_RX_BUFFER], |
| tcp_rx_buffer: vec![0u8; SOCKET_TX_RX_BUFFER], |
| udp_tx_payload_buffer: vec![0u8; ETHERNET_FRAME_SIZE], |
| udp_rx_payload_buffer: vec![0u8; ETHERNET_FRAME_SIZE], |
| udp_tx_metadata_buffer: vec![PacketMetadata::EMPTY; 1], |
| udp_rx_metadata_buffer: vec![PacketMetadata::EMPTY; 1], |
| socket_storage: Default::default(), |
| // Allocates 7(chosen randomly) extra TX frames. Revisits if it is not enough. |
| efi_net_dev: EfiNetworkDevice::new(snp, 7, &efi_entry), |
| timestamp, |
| notify_fn: None, |
| notify: None, |
| }) |
| } |
| |
| /// Creates an instance of [EfiTcpSocket]. |
| fn create_socket(&'b mut self) -> Result<EfiTcpSocket<'a, 'b>> { |
| // Resets network timestamp to 0. |
| let _ = self.timestamp.swap(0, Ordering::Relaxed); |
| |
| // Initializes notification functions. |
| if self.notify_fn.is_none() { |
| self.notify_fn = Some(Box::new(|_: EfiEvent| { |
| self.timestamp.fetch_add( |
| NETWORK_TIMESTAMP_UPDATE_PERIOD.as_millis() as u64, |
| Ordering::Relaxed, |
| ); |
| })); |
| self.notify = Some(EventNotify::new(Tpl::Callback, self.notify_fn.as_mut().unwrap())); |
| } |
| |
| // Creates a timer event for updating the global timestamp. |
| let bs = self.efi_entry.system_table().boot_services(); |
| // SAFETY: the notification callback in `notify_fn` initialized above never allocates, |
| // deallocates, or panics. |
| let _time_update_event = unsafe { |
| bs.create_event_with_notification( |
| EventType::TimerNotifySignal, |
| self.notify.as_mut().unwrap(), |
| ) |
| }?; |
| bs.set_timer( |
| &_time_update_event, |
| EFI_TIMER_DELAY_TIMER_PERIODIC, |
| NETWORK_TIMESTAMP_UPDATE_PERIOD, |
| )?; |
| |
| // 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(self.efi_net_dev.protocol.mode()?.current_address); |
| // Configures smoltcp network interface. |
| let mut interface = Interface::new( |
| Config::new(ll_mac.into()), |
| &mut self.efi_net_dev, |
| to_smoltcp_instant(0), |
| ); |
| interface.update_ip_addrs(|ip_addrs| ip_addrs.push(IpCidr::new(ll_ip6_addr, 64)).unwrap()); |
| |
| // Generates Fuchsia Fastboot MDNS packet. |
| let eth_mac = ll_mac.as_bytes(); |
| let fuchsia_node_name = format!( |
| "fuchsia-{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}", |
| eth_mac[0], eth_mac[1], eth_mac[2], eth_mac[3], eth_mac[4], eth_mac[5] |
| ); |
| let fuchsia_fastboot_mdns_packet = |
| fuchsia_fastboot_mdns_packet(fuchsia_node_name.as_str(), ll_ip6_addr.as_bytes())? |
| .into(); |
| |
| // Creates sockets. |
| let mut socket_set = SocketSet::new(&mut self.socket_storage[..]); |
| // Creates a TCP socket for fastboot over TCP. |
| let tx_socket_buffer = SocketBuffer::new(&mut self.tcp_tx_buffer[..]); |
| let rx_socket_buffer = SocketBuffer::new(&mut self.tcp_rx_buffer[..]); |
| let tcp_socket = TcpSocket::new(rx_socket_buffer, tx_socket_buffer); |
| let _ = socket_set.add(tcp_socket); |
| // Creates a UDP socket for MDNS broadcast. |
| let udp_tx_packet_buffer = PacketBuffer::new( |
| &mut self.udp_tx_metadata_buffer[..], |
| &mut self.udp_tx_payload_buffer[..], |
| ); |
| let udp_rx_packet_buffer = PacketBuffer::new( |
| &mut self.udp_rx_metadata_buffer[..], |
| &mut self.udp_rx_payload_buffer[..], |
| ); |
| let udp_socket = UdpSocket::new(udp_rx_packet_buffer, udp_tx_packet_buffer); |
| let _ = socket_set.add(udp_socket); |
| Ok(EfiTcpSocket { |
| efi_entry: self.efi_entry, |
| efi_net_dev: &mut self.efi_net_dev, |
| interface, |
| socket_set, |
| io_yield_counter: YieldCounter::new(u64::MAX), |
| last_listen_timestamp: None, |
| _time_update_event, |
| timestamp: self.timestamp, |
| fuchsia_fastboot_mdns_packet, |
| }) |
| } |
| } |
| |
| /// The GBL network stack. |
| /// |
| /// # Lifetimes |
| /// |
| /// * `'a`: Lifetime of `efi_entry` borrowed. |
| /// * `'b`: Lifetime of Self. |
| /// * `'c`: Lifetime of external timestamp borrowed. |
| #[derive(Default)] |
| pub struct EfiGblNetwork<'a, 'b, 'c>(Option<EfiGblNetworkInternal<'a, 'b, 'c>>); |
| |
| impl<'a, 'b, 'c: 'b> EfiGblNetwork<'a, 'b, 'c> { |
| /// Initializes GBL network and creates GBL sockets. |
| /// |
| /// # Args: |
| /// |
| /// * `efi_entry`: A [EfiEntry]. |
| /// * `ts`: A reference to an [AtomicU64]. |
| pub fn init( |
| &'b mut self, |
| efi_entry: &'a EfiEntry, |
| timestamp: &'c AtomicU64, |
| ) -> Result<EfiTcpSocket<'a, 'b>> { |
| // Drops any existing network first to release the global event notify function. |
| self.0 = None; |
| self.0 = Some(EfiGblNetworkInternal::new(efi_entry, timestamp)?); |
| self.0.as_mut().unwrap().create_socket() |
| } |
| } |