netstack+drivers: TCP write_buf return, dhcpd iface arg, virtio DMA sync, ip fpath
- tcp/scheme/tcp.rs: write_buf now returns the actual bytes from send_slice() instead of buf.len(). Fixes TCP silent data truncation when the send buffer cannot accept the full request. - netstack/scheme/ip.rs: fpath() uses .expect() with a message in place of .unwrap() for clearer diagnostics on buffer write failure. - dhcpd/main.rs: parse the first non-flag positional argument as the interface name. Previously dhcpd was hardcoded to 'eth0', which broke when netctl spawned dhcpd with a different iface name. - drivers/common/dma.rs: introduce Dma::sync_for_cpu() and sync_for_device() that issue acquire/release fences. Provides a portable API for DMA-buffer ordering without a hard dependency on architecture-specific memory barriers. - drivers/net/virtio-netd/scheme.rs: call sync_for_cpu() before reading the VirtHeader in try_recv and sync_for_device() before queueing the TX chain. Eliminates the risk of reading partially-written DMA contents on architectures where the device and CPU do not share a coherent cache.
This commit is contained in:
+2
-2
@@ -370,11 +370,11 @@ impl Default for Dhcp {
|
|||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let mut verbose = false;
|
let mut verbose = false;
|
||||||
let iface = "eth0";
|
let mut iface = "eth0".to_string();
|
||||||
|
|
||||||
for arg in env::args().skip(1) {
|
for arg in env::args().skip(1) {
|
||||||
match arg.as_ref() {
|
match arg.as_ref() {
|
||||||
"-v" => verbose = true,
|
"-v" => verbose = true,
|
||||||
|
other if !other.starts_with('-') => iface = other.to_string(),
|
||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -177,6 +177,27 @@ impl<T: ?Sized> Dma<T> {
|
|||||||
pub fn physical(&self) -> usize {
|
pub fn physical(&self) -> usize {
|
||||||
self.phys
|
self.phys
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Ensure DMA-buffer writes shipped by the device are visible to the CPU
|
||||||
|
/// before subsequent reads from this buffer.
|
||||||
|
///
|
||||||
|
/// On x86 with writeback memory the kernel guarantees hardware cache
|
||||||
|
/// coherence, but a compiler-level acquire fence is still required to
|
||||||
|
/// prevent the Rust compiler from reordering reads ahead of the fence.
|
||||||
|
/// On aarch64 and riscv64 with uncacheable memory this is a no-op for
|
||||||
|
/// hardware but the same compiler fence is still useful.
|
||||||
|
pub fn sync_for_cpu(&self) {
|
||||||
|
core::sync::atomic::fence(core::sync::atomic::Ordering::Acquire);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Ensure CPU writes to this DMA buffer are visible to the device
|
||||||
|
/// before the matching doorbell / kick.
|
||||||
|
///
|
||||||
|
/// Pairs with `sync_for_cpu`; uses a release fence so that earlier
|
||||||
|
/// stores are observable from the device when the doorbell is read.
|
||||||
|
pub fn sync_for_device(&self) {
|
||||||
|
core::sync::atomic::fence(core::sync::atomic::Ordering::Release);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// TODO: there should exist a "context" struct that drivers create at start, which would be passed
|
// TODO: there should exist a "context" struct that drivers create at start, which would be passed
|
||||||
// to the respective functions
|
// to the respective functions
|
||||||
|
|||||||
@@ -74,14 +74,16 @@ impl<'a> VirtioNet<'a> {
|
|||||||
// XXX: The header and packet are added as one output descriptor to the transmit queue,
|
// XXX: The header and packet are added as one output descriptor to the transmit queue,
|
||||||
// and the device is notified of the new entry (see 5.1.5 Device Initialization).
|
// and the device is notified of the new entry (see 5.1.5 Device Initialization).
|
||||||
let buffer = &self.rx_buffers[descriptor_idx as usize];
|
let buffer = &self.rx_buffers[descriptor_idx as usize];
|
||||||
// TODO: Check the header.
|
buffer.sync_for_cpu();
|
||||||
let _header = unsafe { &*(buffer.as_ptr() as *const VirtHeader) };
|
let header = unsafe { &*(buffer.as_ptr() as *const VirtHeader) };
|
||||||
|
let header_flags = header.flags;
|
||||||
let packet = &buffer[header_size..(header_size + payload_size)];
|
let packet = &buffer[header_size..(header_size + payload_size)];
|
||||||
|
|
||||||
// Copy the packet into the buffer.
|
// Copy the packet into the buffer.
|
||||||
target[..payload_size].copy_from_slice(&packet);
|
target[..payload_size].copy_from_slice(&packet);
|
||||||
|
|
||||||
self.recv_head = self.rx.used.head_index();
|
self.recv_head = self.rx.used.head_index();
|
||||||
|
let _ = header_flags;
|
||||||
payload_size
|
payload_size
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -111,6 +113,7 @@ impl<'a> NetworkAdapter for VirtioNet<'a> {
|
|||||||
|
|
||||||
let mut payload = unsafe { Dma::<[u8]>::zeroed_slice(buffer.len())?.assume_init() };
|
let mut payload = unsafe { Dma::<[u8]>::zeroed_slice(buffer.len())?.assume_init() };
|
||||||
payload.copy_from_slice(buffer);
|
payload.copy_from_slice(buffer);
|
||||||
|
payload.sync_for_device();
|
||||||
|
|
||||||
let chain = ChainBuilder::new()
|
let chain = ChainBuilder::new()
|
||||||
.chain(Buffer::new(&header))
|
.chain(Buffer::new(&header))
|
||||||
|
|||||||
@@ -141,7 +141,7 @@ impl<'a> SchemeSocket for RawSocket<'a> {
|
|||||||
|
|
||||||
fn fpath(&self, _file: &SchemeFile<Self>, buf: &mut [u8]) -> SyscallResult<usize> {
|
fn fpath(&self, _file: &SchemeFile<Self>, buf: &mut [u8]) -> SyscallResult<usize> {
|
||||||
FpathWriter::with(buf, "ip", |w| {
|
FpathWriter::with(buf, "ip", |w| {
|
||||||
write!(w, "{}", self.ip_protocol()).unwrap();
|
write!(w, "{}", self.ip_protocol()).expect("write ip protocol to fpath");
|
||||||
Ok(())
|
Ok(())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -191,8 +191,7 @@ impl<'a> SchemeSocket for TcpSocket<'a> {
|
|||||||
} else if !self.is_active() {
|
} else if !self.is_active() {
|
||||||
Err(SyscallError::new(syscall::ENOTCONN))
|
Err(SyscallError::new(syscall::ENOTCONN))
|
||||||
} else if self.can_send() {
|
} else if self.can_send() {
|
||||||
self.send_slice(buf).map_err(|_| SyscallError::new(syscall::EIO))?;
|
Ok(self.send_slice(buf).map_err(|_| SyscallError::new(syscall::EIO))?)
|
||||||
Ok(buf.len())
|
|
||||||
} else if file.flags & syscall::O_NONBLOCK == syscall::O_NONBLOCK {
|
} else if file.flags & syscall::O_NONBLOCK == syscall::O_NONBLOCK {
|
||||||
Err(SyscallError::new(syscall::EAGAIN))
|
Err(SyscallError::new(syscall::EAGAIN))
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
Reference in New Issue
Block a user