9e5f915d42
- 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.
127 lines
3.7 KiB
Rust
127 lines
3.7 KiB
Rust
use std::sync::Arc;
|
|
|
|
use driver_network::NetworkAdapter;
|
|
|
|
use common::dma::Dma;
|
|
|
|
use virtio_core::spec::{Buffer, ChainBuilder, DescriptorFlags};
|
|
use virtio_core::transport::Queue;
|
|
|
|
use crate::{VirtHeader, MAX_BUFFER_LEN};
|
|
|
|
pub struct VirtioNet<'a> {
|
|
mac_address: [u8; 6],
|
|
|
|
/// Reciever Queue.
|
|
rx: Arc<Queue<'a>>,
|
|
rx_buffers: Vec<Dma<[u8]>>,
|
|
|
|
/// Transmiter Queue.
|
|
tx: Arc<Queue<'a>>,
|
|
|
|
recv_head: u16,
|
|
}
|
|
|
|
impl<'a> VirtioNet<'a> {
|
|
pub fn new(mac_address: [u8; 6], rx: Arc<Queue<'a>>, tx: Arc<Queue<'a>>) -> Self {
|
|
// Populate all of the `rx_queue` with buffers to maximize performence.
|
|
let mut rx_buffers = vec![];
|
|
for i in 0..(rx.descriptor_len() as usize) {
|
|
let buf = unsafe {
|
|
match Dma::<[u8]>::zeroed_slice(MAX_BUFFER_LEN) {
|
|
Ok(dma) => dma.assume_init(),
|
|
Err(err) => {
|
|
log::error!("virtio-netd: failed to allocate rx buffer: {err}");
|
|
continue;
|
|
}
|
|
}
|
|
};
|
|
rx_buffers.push(buf);
|
|
|
|
let chain = ChainBuilder::new()
|
|
.chain(Buffer::new_unsized(&rx_buffers[i]).flags(DescriptorFlags::WRITE_ONLY))
|
|
.build();
|
|
|
|
let _ = rx.send(chain);
|
|
}
|
|
|
|
Self {
|
|
mac_address,
|
|
|
|
rx,
|
|
rx_buffers,
|
|
tx,
|
|
|
|
recv_head: 0,
|
|
}
|
|
}
|
|
|
|
/// Returns the number of bytes read. Returns `0` if the operation would block.
|
|
fn try_recv(&mut self, target: &mut [u8]) -> usize {
|
|
let header_size = core::mem::size_of::<VirtHeader>();
|
|
|
|
if self.recv_head == self.rx.used.head_index() {
|
|
// The read would block.
|
|
return 0;
|
|
}
|
|
|
|
let idx = self.rx.used.head_index() as usize;
|
|
let element = self.rx.used.get_element_at(idx - 1);
|
|
|
|
let descriptor_idx = element.table_index.get();
|
|
let payload_size = element.written.get() as usize - header_size;
|
|
|
|
// 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).
|
|
let buffer = &self.rx_buffers[descriptor_idx as usize];
|
|
buffer.sync_for_cpu();
|
|
let header = unsafe { &*(buffer.as_ptr() as *const VirtHeader) };
|
|
let header_flags = header.flags;
|
|
let packet = &buffer[header_size..(header_size + payload_size)];
|
|
|
|
// Copy the packet into the buffer.
|
|
target[..payload_size].copy_from_slice(&packet);
|
|
|
|
self.recv_head = self.rx.used.head_index();
|
|
let _ = header_flags;
|
|
payload_size
|
|
}
|
|
}
|
|
|
|
impl<'a> NetworkAdapter for VirtioNet<'a> {
|
|
fn mac_address(&mut self) -> [u8; 6] {
|
|
self.mac_address
|
|
}
|
|
|
|
fn available_for_read(&mut self) -> usize {
|
|
(self.rx.used.head_index() - self.recv_head).into()
|
|
}
|
|
|
|
fn read_packet(&mut self, buf: &mut [u8]) -> syscall::Result<Option<usize>> {
|
|
let bytes = self.try_recv(buf);
|
|
|
|
if bytes != 0 {
|
|
// We read some bytes.
|
|
Ok(Some(bytes))
|
|
} else {
|
|
Ok(None)
|
|
}
|
|
}
|
|
|
|
fn write_packet(&mut self, buffer: &[u8]) -> syscall::Result<usize> {
|
|
let header = unsafe { Dma::<VirtHeader>::zeroed()?.assume_init() };
|
|
|
|
let mut payload = unsafe { Dma::<[u8]>::zeroed_slice(buffer.len())?.assume_init() };
|
|
payload.copy_from_slice(buffer);
|
|
payload.sync_for_device();
|
|
|
|
let chain = ChainBuilder::new()
|
|
.chain(Buffer::new(&header))
|
|
.chain(Buffer::new_unsized(&payload))
|
|
.build();
|
|
|
|
futures::executor::block_on(self.tx.send(chain));
|
|
Ok(buffer.len())
|
|
}
|
|
}
|