blob: 75862ff247b4f838784b18266330a9960590d669 [file] [log] [blame]
use r_efi::efi::{self, Status};
use r_efi::protocols::tcp4;
use crate::io;
use crate::net::SocketAddrV4;
use crate::ptr::NonNull;
use crate::sync::atomic::{AtomicBool, Ordering};
use crate::sys::pal::helpers;
use crate::time::{Duration, Instant};
const TYPE_OF_SERVICE: u8 = 8;
const TIME_TO_LIVE: u8 = 255;
pub(crate) struct Tcp4 {
protocol: NonNull<tcp4::Protocol>,
flag: AtomicBool,
#[expect(dead_code)]
service_binding: helpers::ServiceProtocol,
}
const DEFAULT_ADDR: efi::Ipv4Address = efi::Ipv4Address { addr: [0u8; 4] };
impl Tcp4 {
pub(crate) fn new() -> io::Result<Self> {
let service_binding = helpers::ServiceProtocol::open(tcp4::SERVICE_BINDING_PROTOCOL_GUID)?;
let protocol = helpers::open_protocol(service_binding.child_handle(), tcp4::PROTOCOL_GUID)?;
Ok(Self { service_binding, protocol, flag: AtomicBool::new(false) })
}
pub(crate) fn configure(
&self,
active: bool,
remote_address: Option<&SocketAddrV4>,
station_address: Option<&SocketAddrV4>,
) -> io::Result<()> {
let protocol = self.protocol.as_ptr();
let (remote_address, remote_port) = if let Some(x) = remote_address {
(helpers::ipv4_to_r_efi(*x.ip()), x.port())
} else {
(DEFAULT_ADDR, 0)
};
// FIXME: Remove when passive connections with proper subnet handling are added
assert!(station_address.is_none());
let use_default_address = efi::Boolean::TRUE;
let (station_address, station_port) = (DEFAULT_ADDR, 0);
let subnet_mask = helpers::ipv4_to_r_efi(crate::net::Ipv4Addr::new(0, 0, 0, 0));
let mut config_data = tcp4::ConfigData {
type_of_service: TYPE_OF_SERVICE,
time_to_live: TIME_TO_LIVE,
access_point: tcp4::AccessPoint {
use_default_address,
remote_address,
remote_port,
active_flag: active.into(),
station_address,
station_port,
subnet_mask,
},
control_option: crate::ptr::null_mut(),
};
let r = unsafe { ((*protocol).configure)(protocol, &mut config_data) };
if r.is_error() { Err(crate::io::Error::from_raw_os_error(r.as_usize())) } else { Ok(()) }
}
pub(crate) fn get_mode_data(&self) -> io::Result<tcp4::ConfigData> {
let mut config_data = tcp4::ConfigData::default();
let protocol = self.protocol.as_ptr();
let r = unsafe {
((*protocol).get_mode_data)(
protocol,
crate::ptr::null_mut(),
&mut config_data,
crate::ptr::null_mut(),
crate::ptr::null_mut(),
crate::ptr::null_mut(),
)
};
if r.is_error() { Err(io::Error::from_raw_os_error(r.as_usize())) } else { Ok(config_data) }
}
pub(crate) fn connect(&self, timeout: Option<Duration>) -> io::Result<()> {
let evt = unsafe { self.create_evt() }?;
let completion_token =
tcp4::CompletionToken { event: evt.as_ptr(), status: Status::SUCCESS };
let protocol = self.protocol.as_ptr();
let mut conn_token = tcp4::ConnectionToken { completion_token };
let r = unsafe { ((*protocol).connect)(protocol, &mut conn_token) };
if r.is_error() {
return Err(io::Error::from_raw_os_error(r.as_usize()));
}
unsafe { self.wait_or_cancel(timeout, &mut conn_token.completion_token) }?;
if completion_token.status.is_error() {
Err(io::Error::from_raw_os_error(completion_token.status.as_usize()))
} else {
Ok(())
}
}
pub(crate) fn write(&self, buf: &[u8], timeout: Option<Duration>) -> io::Result<usize> {
let evt = unsafe { self.create_evt() }?;
let completion_token =
tcp4::CompletionToken { event: evt.as_ptr(), status: Status::SUCCESS };
let data_len = u32::try_from(buf.len()).unwrap_or(u32::MAX);
let fragment = tcp4::FragmentData {
fragment_length: data_len,
fragment_buffer: buf.as_ptr().cast::<crate::ffi::c_void>().cast_mut(),
};
let mut tx_data = tcp4::TransmitData {
push: r_efi::efi::Boolean::FALSE,
urgent: r_efi::efi::Boolean::FALSE,
data_length: data_len,
fragment_count: 1,
fragment_table: [fragment],
};
let protocol = self.protocol.as_ptr();
let mut token = tcp4::IoToken {
completion_token,
packet: tcp4::IoTokenPacket {
tx_data: (&raw mut tx_data).cast::<tcp4::TransmitData<0>>(),
},
};
let r = unsafe { ((*protocol).transmit)(protocol, &mut token) };
if r.is_error() {
return Err(io::Error::from_raw_os_error(r.as_usize()));
}
unsafe { self.wait_or_cancel(timeout, &mut token.completion_token) }?;
if completion_token.status.is_error() {
Err(io::Error::from_raw_os_error(completion_token.status.as_usize()))
} else {
Ok(data_len as usize)
}
}
pub(crate) fn read(&self, buf: &mut [u8], timeout: Option<Duration>) -> io::Result<usize> {
let evt = unsafe { self.create_evt() }?;
let completion_token =
tcp4::CompletionToken { event: evt.as_ptr(), status: Status::SUCCESS };
let data_len = u32::try_from(buf.len()).unwrap_or(u32::MAX);
let fragment = tcp4::FragmentData {
fragment_length: data_len,
fragment_buffer: buf.as_mut_ptr().cast::<crate::ffi::c_void>(),
};
let mut tx_data = tcp4::ReceiveData {
urgent_flag: r_efi::efi::Boolean::FALSE,
data_length: data_len,
fragment_count: 1,
fragment_table: [fragment],
};
let protocol = self.protocol.as_ptr();
let mut token = tcp4::IoToken {
completion_token,
packet: tcp4::IoTokenPacket {
rx_data: (&raw mut tx_data).cast::<tcp4::ReceiveData<0>>(),
},
};
let r = unsafe { ((*protocol).receive)(protocol, &mut token) };
if r.is_error() {
return Err(io::Error::from_raw_os_error(r.as_usize()));
}
unsafe { self.wait_or_cancel(timeout, &mut token.completion_token) }?;
if completion_token.status.is_error() {
Err(io::Error::from_raw_os_error(completion_token.status.as_usize()))
} else {
Ok(data_len as usize)
}
}
/// Wait for an event to finish. This is checked by an atomic boolean that is supposed to be set
/// to true in the event callback.
///
/// Optionally, allow specifying a timeout.
///
/// If a timeout is provided, the operation (specified by its `EFI_TCP4_COMPLETION_TOKEN`) is
/// canceled and Error of kind TimedOut is returned.
///
/// # SAFETY
///
/// Pointer to a valid `EFI_TCP4_COMPLETION_TOKEN`
unsafe fn wait_or_cancel(
&self,
timeout: Option<Duration>,
token: *mut tcp4::CompletionToken,
) -> io::Result<()> {
if !self.wait_for_flag(timeout) {
let _ = unsafe { self.cancel(token) };
return Err(io::Error::new(io::ErrorKind::TimedOut, "Operation Timed out"));
}
Ok(())
}
/// Abort an asynchronous connection, listen, transmission or receive request.
///
/// If token is NULL, then all pending tokens issued by EFI_TCP4_PROTOCOL.Connect(),
/// EFI_TCP4_PROTOCOL.Accept(), EFI_TCP4_PROTOCOL.Transmit() or EFI_TCP4_PROTOCOL.Receive() are
/// aborted.
///
/// # SAFETY
///
/// Pointer to a valid `EFI_TCP4_COMPLETION_TOKEN` or NULL
unsafe fn cancel(&self, token: *mut tcp4::CompletionToken) -> io::Result<()> {
let protocol = self.protocol.as_ptr();
let r = unsafe { ((*protocol).cancel)(protocol, token) };
if r.is_error() {
return Err(io::Error::from_raw_os_error(r.as_usize()));
} else {
Ok(())
}
}
unsafe fn create_evt(&self) -> io::Result<helpers::OwnedEvent> {
self.flag.store(false, Ordering::Relaxed);
helpers::OwnedEvent::new(
efi::EVT_NOTIFY_SIGNAL,
efi::TPL_CALLBACK,
Some(toggle_atomic_flag),
Some(unsafe { NonNull::new_unchecked(self.flag.as_ptr().cast()) }),
)
}
fn wait_for_flag(&self, timeout: Option<Duration>) -> bool {
let start = Instant::now();
while !self.flag.load(Ordering::Relaxed) {
let _ = self.poll();
if let Some(t) = timeout {
if Instant::now().duration_since(start) >= t {
return false;
}
}
}
true
}
fn poll(&self) -> io::Result<()> {
let protocol = self.protocol.as_ptr();
let r = unsafe { ((*protocol).poll)(protocol) };
if r.is_error() { Err(io::Error::from_raw_os_error(r.as_usize())) } else { Ok(()) }
}
}
extern "efiapi" fn toggle_atomic_flag(_: r_efi::efi::Event, ctx: *mut crate::ffi::c_void) {
let flag = unsafe { AtomicBool::from_ptr(ctx.cast()) };
flag.store(true, Ordering::Relaxed);
}