From d94ce0a56f303ee2266a8bd16afd5f35d1e59588 Mon Sep 17 00:00:00 2001 From: Yuekai Jia Date: Tue, 9 May 2023 17:28:42 +0800 Subject: [PATCH 01/14] Introduce `VirtIONetRaw` to allow custom NIC buffer management --- examples/aarch64/src/main.rs | 17 ++- examples/riscv/src/main.rs | 46 +++--- examples/riscv/src/tcp.rs | 8 +- examples/x86_64/Makefile | 9 +- examples/x86_64/src/main.rs | 46 +++--- examples/x86_64/src/tcp.rs | 8 +- src/device/mod.rs | 1 - src/device/net/dev.rs | 104 ++++++++++++++ src/device/net/dev_raw.rs | 262 +++++++++++++++++++++++++++++++++++ src/device/net/mod.rs | 149 ++++++++++++++++++++ src/device/net/net_buf.rs | 55 ++++++++ 11 files changed, 649 insertions(+), 56 deletions(-) create mode 100644 src/device/net/dev.rs create mode 100644 src/device/net/dev_raw.rs create mode 100644 src/device/net/mod.rs create mode 100644 src/device/net/net_buf.rs diff --git a/examples/aarch64/src/main.rs b/examples/aarch64/src/main.rs index bc2e2156..c3ec0b3c 100644 --- a/examples/aarch64/src/main.rs +++ b/examples/aarch64/src/main.rs @@ -137,7 +137,7 @@ fn virtio_device(transport: impl Transport) { match transport.device_type() { DeviceType::Block => virtio_blk(transport), DeviceType::GPU => virtio_gpu(transport), - // DeviceType::Network => virtio_net(transport), // currently is unsupported without alloc + DeviceType::Network => virtio_net(transport), DeviceType::Console => virtio_console(transport), DeviceType::Socket => match virtio_socket(transport) { Ok(()) => info!("virtio-socket test finished successfully"), @@ -192,6 +192,21 @@ fn virtio_gpu(transport: T) { info!("virtio-gpu test finished"); } +fn virtio_net(transport: T) { + let mut net = + VirtIONetRaw::::new(transport).expect("failed to create net driver"); + let mut buf = [0u8; 2048]; + let (hdr_len, pkt_len) = net.receive_wait(&mut buf).expect("failed to recv"); + info!( + "recv {} bytes: {:02x?}", + pkt_len, + &buf[hdr_len..hdr_len + pkt_len] + ); + net.transmit_wait(&buf[..hdr_len + pkt_len]) + .expect("failed to send"); + info!("virtio-net test finished"); +} + fn virtio_console(transport: T) { let mut console = VirtIOConsole::::new(transport).expect("Failed to create console driver"); diff --git a/examples/riscv/src/main.rs b/examples/riscv/src/main.rs index 1eee763b..8e66ea2f 100644 --- a/examples/riscv/src/main.rs +++ b/examples/riscv/src/main.rs @@ -13,7 +13,7 @@ use core::ptr::NonNull; use fdt::{node::FdtNode, standard_nodes::Compatible, Fdt}; use log::LevelFilter; use virtio_drivers::{ - device::{blk::VirtIOBlk, gpu::VirtIOGpu, input::VirtIOInput, net::VirtIONet}, + device::{blk::VirtIOBlk, gpu::VirtIOGpu, input::VirtIOInput}, transport::{ mmio::{MmioTransport, VirtIOHeader}, DeviceType, Transport, @@ -26,7 +26,6 @@ mod virtio_impl; #[cfg(feature = "tcp")] mod tcp; -const NET_BUFFER_LEN: usize = 2048; const NET_QUEUE_SIZE: usize = 16; #[no_mangle] @@ -146,29 +145,34 @@ fn virtio_input(transport: T) { } fn virtio_net(transport: T) { - let net = VirtIONet::::new(transport, NET_BUFFER_LEN) - .expect("failed to create net driver"); - info!("MAC address: {:02x?}", net.mac_address()); - #[cfg(not(feature = "tcp"))] { - let mut net = net; - loop { - match net.receive() { - Ok(buf) => { - info!("RECV {} bytes: {:02x?}", buf.packet_len(), buf.packet()); - let tx_buf = virtio_drivers::device::net::TxBuffer::from(buf.packet()); - net.send(tx_buf).expect("failed to send"); - net.recycle_rx_buffer(buf).unwrap(); - break; - } - Err(virtio_drivers::Error::NotReady) => continue, - Err(err) => panic!("failed to recv: {:?}", err), - } - } + let mut net = + virtio_drivers::device::net::VirtIONetRaw::::new(transport) + .expect("failed to create net driver"); + info!("MAC address: {:02x?}", net.mac_address()); + + let mut buf = [0u8; 2048]; + let (hdr_len, pkt_len) = net.receive_wait(&mut buf).expect("failed to recv"); + info!( + "recv {} bytes: {:02x?}", + pkt_len, + &buf[hdr_len..hdr_len + pkt_len] + ); + net.transmit_wait(&buf[..hdr_len + pkt_len]) + .expect("failed to send"); info!("virtio-net test finished"); } #[cfg(feature = "tcp")] - tcp::test_echo_server(net); + { + const NET_BUFFER_LEN: usize = 2048; + let net = virtio_drivers::device::net::VirtIONet::::new( + transport, + NET_BUFFER_LEN, + ) + .expect("failed to create net driver"); + info!("MAC address: {:02x?}", net.mac_address()); + tcp::test_echo_server(net); + } } diff --git a/examples/riscv/src/tcp.rs b/examples/riscv/src/tcp.rs index 81545e4b..66c89a71 100644 --- a/examples/riscv/src/tcp.rs +++ b/examples/riscv/src/tcp.rs @@ -9,7 +9,7 @@ use smoltcp::iface::{Config, Interface, SocketSet}; use smoltcp::phy::{Device, DeviceCapabilities, Medium, RxToken, TxToken}; use smoltcp::wire::{EthernetAddress, IpAddress, IpCidr, Ipv4Address}; use smoltcp::{socket::tcp, time::Instant}; -use virtio_drivers::device::net::{RxBuffer, VirtIONet}; +use virtio_drivers::device::net::{NetBuffer, VirtIONet}; use virtio_drivers::{transport::Transport, Error}; use super::{HalImpl, NET_QUEUE_SIZE}; @@ -64,7 +64,7 @@ impl Device for DeviceWrapper { } } -struct VirtioRxToken(Rc>>, RxBuffer); +struct VirtioRxToken(Rc>>, NetBuffer); struct VirtioTxToken(Rc>>); impl RxToken for VirtioRxToken { @@ -90,10 +90,10 @@ impl TxToken for VirtioTxToken { F: FnOnce(&mut [u8]) -> R, { let mut dev = self.0.borrow_mut(); - let mut tx_buf = dev.new_tx_buffer(len); + let mut tx_buf = dev.new_tx_buffer(len).unwrap(); let result = f(tx_buf.packet_mut()); trace!("SEND {} bytes: {:02X?}", len, tx_buf.packet()); - dev.send(tx_buf).unwrap(); + dev.transmit_wait(tx_buf).unwrap(); result } } diff --git a/examples/x86_64/Makefile b/examples/x86_64/Makefile index 35449231..e22d01d4 100644 --- a/examples/x86_64/Makefile +++ b/examples/x86_64/Makefile @@ -3,7 +3,7 @@ target := x86_64-unknown-none mode := release kernel := target/$(target)/$(mode)/$(arch) img := target/$(target)/$(mode)/img -accel := on +accel ?= on tcp ?= off sysroot := $(shell rustc --print sysroot) @@ -20,9 +20,10 @@ else BUILD_ARGS += --no-default-features endif -VSOCK_BUILD_ARGS = -ifeq ($(mode), release) - VSOCK_BUILD_ARGS += --release +ifeq ($(tcp), on) + BUILD_ARGS += --features tcp +else + BUILD_ARGS += --no-default-features endif QEMU_ARGS += \ diff --git a/examples/x86_64/src/main.rs b/examples/x86_64/src/main.rs index e9397d73..5c79b20f 100644 --- a/examples/x86_64/src/main.rs +++ b/examples/x86_64/src/main.rs @@ -18,7 +18,7 @@ mod tcp; use self::hal::HalImpl; use virtio_drivers::{ - device::{blk::VirtIOBlk, gpu::VirtIOGpu, net::VirtIONet}, + device::{blk::VirtIOBlk, gpu::VirtIOGpu}, transport::{ pci::{ bus::{BarInfo, Cam, Command, DeviceFunction, PciRoot}, @@ -35,7 +35,6 @@ use virtio_drivers::{ /// TODO: get it from ACPI MCFG table. const MMCONFIG_BASE: usize = 0xB000_0000; -const NET_BUFFER_LEN: usize = 2048; const NET_QUEUE_SIZE: usize = 16; fn system_off() -> ! { @@ -117,31 +116,36 @@ fn virtio_gpu(transport: T) { } fn virtio_net(transport: T) { - let net = VirtIONet::::new(transport, NET_BUFFER_LEN) - .expect("failed to create net driver"); - info!("MAC address: {:02x?}", net.mac_address()); - #[cfg(not(feature = "tcp"))] { - let mut net = net; - loop { - match net.receive() { - Ok(buf) => { - info!("RECV {} bytes: {:02x?}", buf.packet_len(), buf.packet()); - let tx_buf = virtio_drivers::device::net::TxBuffer::from(buf.packet()); - net.send(tx_buf).expect("failed to send"); - net.recycle_rx_buffer(buf).unwrap(); - break; - } - Err(virtio_drivers::Error::NotReady) => continue, - Err(err) => panic!("failed to recv: {:?}", err), - } - } + let mut net = + virtio_drivers::device::net::VirtIONetRaw::::new(transport) + .expect("failed to create net driver"); + info!("MAC address: {:02x?}", net.mac_address()); + + let mut buf = [0u8; 2048]; + let (hdr_len, pkt_len) = net.receive_wait(&mut buf).expect("failed to recv"); + info!( + "recv {} bytes: {:02x?}", + pkt_len, + &buf[hdr_len..hdr_len + pkt_len] + ); + net.transmit_wait(&buf[..hdr_len + pkt_len]) + .expect("failed to send"); info!("virtio-net test finished"); } #[cfg(feature = "tcp")] - tcp::test_echo_server(net); + { + const NET_BUFFER_LEN: usize = 2048; + let net = virtio_drivers::device::net::VirtIONet::::new( + transport, + NET_BUFFER_LEN, + ) + .expect("failed to create net driver"); + info!("MAC address: {:02x?}", net.mac_address()); + tcp::test_echo_server(net); + } } fn enumerate_pci(mmconfig_base: *mut u8) { diff --git a/examples/x86_64/src/tcp.rs b/examples/x86_64/src/tcp.rs index 3c8422b4..30ff6db4 100644 --- a/examples/x86_64/src/tcp.rs +++ b/examples/x86_64/src/tcp.rs @@ -9,7 +9,7 @@ use smoltcp::iface::{Config, Interface, SocketSet}; use smoltcp::phy::{Device, DeviceCapabilities, Medium, RxToken, TxToken}; use smoltcp::wire::{EthernetAddress, IpAddress, IpCidr, Ipv4Address}; use smoltcp::{socket::tcp, time::Instant}; -use virtio_drivers::device::net::{RxBuffer, VirtIONet}; +use virtio_drivers::device::net::{NetBuffer, VirtIONet}; use virtio_drivers::{transport::Transport, Error}; use super::{HalImpl, NET_QUEUE_SIZE}; @@ -64,7 +64,7 @@ impl Device for DeviceWrapper { } } -struct VirtioRxToken(Rc>>, RxBuffer); +struct VirtioRxToken(Rc>>, NetBuffer); struct VirtioTxToken(Rc>>); impl RxToken for VirtioRxToken { @@ -90,10 +90,10 @@ impl TxToken for VirtioTxToken { F: FnOnce(&mut [u8]) -> R, { let mut dev = self.0.borrow_mut(); - let mut tx_buf = dev.new_tx_buffer(len); + let mut tx_buf = dev.new_tx_buffer(len).unwrap(); let result = f(tx_buf.packet_mut()); trace!("SEND {} bytes: {:02X?}", len, tx_buf.packet()); - dev.send(tx_buf).unwrap(); + dev.transmit_wait(tx_buf).unwrap(); result } } diff --git a/src/device/mod.rs b/src/device/mod.rs index 00fa6fe2..e7929707 100644 --- a/src/device/mod.rs +++ b/src/device/mod.rs @@ -7,7 +7,6 @@ pub mod console; pub mod gpu; #[cfg(feature = "alloc")] pub mod input; -#[cfg(feature = "alloc")] pub mod net; pub mod socket; diff --git a/src/device/net/dev.rs b/src/device/net/dev.rs new file mode 100644 index 00000000..d24b9411 --- /dev/null +++ b/src/device/net/dev.rs @@ -0,0 +1,104 @@ +use super::net_buf::NetBuffer; +use super::{EthernetAddress, VirtIONetRaw, NET_HDR_SIZE}; +use crate::{hal::Hal, transport::Transport, Error, Result}; + +/// Driver for a VirtIO block device. +/// +/// Unlike [`VirtIONetRaw`], it uses [`NetBuffer`]s for transmission and +/// reception rather than the raw slices. On initialization, it pre-allocates +/// all receive buffers and puts them all in the receive queue. +pub struct VirtIONet { + inner: VirtIONetRaw, + rx_buffers: [Option; QUEUE_SIZE], +} + +impl VirtIONet { + /// Create a new VirtIO-Net driver. + pub fn new(transport: T, buf_len: usize) -> Result { + let mut inner = VirtIONetRaw::new(transport)?; + + const NONE_BUF: Option = None; + let mut rx_buffers = [NONE_BUF; QUEUE_SIZE]; + for (i, rx_buf_place) in rx_buffers.iter_mut().enumerate() { + let mut rx_buf = NetBuffer::new(buf_len); + // Safe because the buffer lives as long as the queue. + let token = unsafe { inner.receive_begin(rx_buf.as_bytes_mut())? }; + assert_eq!(token, i as u16); + *rx_buf_place = Some(rx_buf); + } + + Ok(VirtIONet { inner, rx_buffers }) + } + + /// Acknowledge interrupt. + pub fn ack_interrupt(&mut self) -> bool { + self.inner.ack_interrupt() + } + + /// Get MAC address. + pub fn mac_address(&self) -> EthernetAddress { + self.inner.mac_address() + } + + /// Whether can transmit packet. + pub fn can_transmit(&self) -> bool { + self.inner.can_transmit() + } + + /// Whether can receive packet. + pub fn can_receive(&self) -> bool { + self.inner.can_receive() + } + + /// Receives a [`NetBuffer`] from network. If currently no data, returns an + /// error with type [`Error::NotReady`]. + /// + /// It will try to pop a buffer that completed data reception in the + /// NIC queue. + pub fn receive(&mut self) -> Result { + if let Some(token) = self.inner.poll_receive() { + let mut rx_buf = self.rx_buffers[token as usize] + .take() + .ok_or(Error::WrongToken)?; + + // Safe because `token` == `rx_buf.idx`, we are passing the same + // buffer as we passed to `VirtQueue::add` and it is still valid. + let (_hdr_len, pkt_len) = + unsafe { self.inner.receive_complete(token, rx_buf.as_bytes_mut())? }; + rx_buf.set_packet_len(pkt_len); + Ok(rx_buf) + } else { + Err(Error::NotReady) + } + } + + /// Gives back the ownership of `rx_buf`, and recycles it for next use. + /// + /// It will add the buffer back to the NIC queue. + pub fn recycle_rx_buffer(&mut self, mut rx_buf: NetBuffer) -> Result { + // Safe because we take the ownership of `rx_buf` back to `rx_buffers`, + // it lives as long as the queue. + let new_token = unsafe { self.inner.receive_begin(rx_buf.as_bytes_mut()) }?; + // `rx_buffers[new_token]` is expected to be `None` since it was taken + // away at `Self::receive()` and has not been added back. + if self.rx_buffers[new_token as usize].is_some() { + return Err(Error::WrongToken); + } + self.rx_buffers[new_token as usize] = Some(rx_buf); + Ok(()) + } + + /// Allocate a new buffer for transmitting. + pub fn new_tx_buffer(&self, packet_len: usize) -> Result { + let mut tx_buf = NetBuffer::new(NET_HDR_SIZE + packet_len); + self.inner.fill_buffer_header(tx_buf.as_bytes_mut())?; + tx_buf.set_packet_len(packet_len); + Ok(tx_buf) + } + + /// Sends a [`NetBuffer`] to the network, and blocks until the request + /// completed. Returns number of bytes transmitted. + pub fn transmit_wait(&mut self, tx_buf: NetBuffer) -> Result { + self.inner.transmit_wait(tx_buf.as_bytes()) + } +} diff --git a/src/device/net/dev_raw.rs b/src/device/net/dev_raw.rs new file mode 100644 index 00000000..620daccf --- /dev/null +++ b/src/device/net/dev_raw.rs @@ -0,0 +1,262 @@ +use super::{Config, EthernetAddress, Features, VirtioNetHdr}; +use super::{MIN_BUFFER_LEN, NET_HDR_SIZE, QUEUE_RECEIVE, QUEUE_TRANSMIT}; +use crate::hal::Hal; +use crate::queue::VirtQueue; +use crate::transport::Transport; +use crate::volatile::volread; +use crate::{Error, Result}; +use log::{debug, info, warn}; +use zerocopy::AsBytes; + +/// Raw driver for a VirtIO block device. +/// +/// This is a raw version of the VirtIONet driver. It provides non-blocking +/// methods for transmitting and receiving raw slices, without the buffer +/// management. For more higher-level fucntions such as receive buffer backing, +/// see [`VirtIONet`]. +/// +/// [`VirtIONet`]: super::VirtIONet +pub struct VirtIONetRaw { + transport: T, + mac: EthernetAddress, + recv_queue: VirtQueue, + xmit_queue: VirtQueue, +} + +impl VirtIONetRaw { + /// Create a new VirtIO-Net driver. + pub fn new(mut transport: T) -> Result { + transport.begin_init(|features| { + let features = Features::from_bits_truncate(features); + info!("Device features {:?}", features); + let supported_features = Features::MAC | Features::STATUS; + (features & supported_features).bits() + }); + // read configuration space + let config = transport.config_space::()?; + let mac; + // Safe because config points to a valid MMIO region for the config space. + unsafe { + mac = volread!(config, mac); + debug!( + "Got MAC={:02x?}, status={:?}", + mac, + volread!(config, status) + ); + } + + let recv_queue = VirtQueue::new(&mut transport, QUEUE_RECEIVE)?; + let xmit_queue = VirtQueue::new(&mut transport, QUEUE_TRANSMIT)?; + transport.finish_init(); + + Ok(VirtIONetRaw { + transport, + mac, + recv_queue, + xmit_queue, + }) + } + + /// Acknowledge interrupt. + pub fn ack_interrupt(&mut self) -> bool { + self.transport.ack_interrupt() + } + + /// Get MAC address. + pub fn mac_address(&self) -> EthernetAddress { + self.mac + } + + /// Whether can transmit packet. + pub fn can_transmit(&self) -> bool { + self.xmit_queue.available_desc() >= 1 + } + + /// Whether can receive packet. + pub fn can_receive(&self) -> bool { + self.recv_queue.can_pop() + } + + /// Whether the length of the receive buffer is valid. + fn check_rx_buf_len(rx_buf: &[u8]) -> Result<()> { + if rx_buf.len() < MIN_BUFFER_LEN { + warn!("Receive buffer len {} is too small", rx_buf.len()); + Err(Error::InvalidParam) + } else { + Ok(()) + } + } + + /// Whether the length of the transmit buffer is valid. + fn check_tx_buf_len(tx_buf: &[u8]) -> Result<()> { + if tx_buf.len() < NET_HDR_SIZE { + warn!("Transmit buffer len {} is too small", tx_buf.len()); + Err(Error::InvalidParam) + } else { + Ok(()) + } + } + + /// Fill the header of the `buffer` with [`VirtioNetHdr`]. + /// + /// If the `buffer` is not large enough, it returns [`Error::InvalidParam`]. + pub fn fill_buffer_header(&self, buffer: &mut [u8]) -> Result { + if buffer.len() < NET_HDR_SIZE { + return Err(Error::InvalidParam); + } + let header = VirtioNetHdr::default(); + buffer[..NET_HDR_SIZE].copy_from_slice(header.as_bytes()); + Ok(NET_HDR_SIZE) + } + + /// Submits a request to transmit a buffer immediately without waiting for + /// the transmission to complete. + /// + /// It will submit request to the VirtIO net device and return a token + /// identifying the position of the first descriptor in the chain. If there + /// are not enough descriptors to allocate, then it returns + /// [`Error::QueueFull`]. + /// + /// The caller needs to fill the `tx_buf` with a header by calling + /// [`fill_buffer_header`] before transmission. Then it calls [`poll_transmit`] + /// with the returned token to check whether the device has finished handling + /// the request. Once it has, the caller must call [`transmit_complete`] with + /// the same buffers before reading the result (transmitted length). + /// + /// # Safety + /// + /// `tx_buf` is still borrowed by the underlying VirtIO net device even after + /// this method returns. Thus, it is the caller's responsibility to guarantee + /// that they are not accessed before the request is completed in order to + /// avoid data races. + /// + /// [`fill_buffer_header`]: Self::fill_buffer_header + /// [`poll_transmit`]: Self::poll_transmit + /// [`transmit_complete`]: Self::transmit_complete + pub unsafe fn transmit_begin(&mut self, tx_buf: &[u8]) -> Result { + Self::check_tx_buf_len(tx_buf)?; + let token = self.xmit_queue.add(&[tx_buf], &mut [])?; + if self.xmit_queue.should_notify() { + self.transport.notify(QUEUE_TRANSMIT); + } + Ok(token) + } + + /// Fetches the token of the next completed transmission request from the + /// used ring and returns it, without removing it from the used ring. If + /// there are no pending completed requests it returns [`None`]. + pub fn poll_transmit(&mut self) -> Option { + self.xmit_queue.peek_used() + } + + /// Completes a transmission operation which was started by [`transmit_begin`]. + /// Returns number of bytes transmitted. + /// + /// # Safety + /// + /// The same buffers must be passed in again as were passed to + /// [`transmit_begin`] when it returned the token. + /// + /// [`transmit_begin`]: Self::transmit_begin + pub unsafe fn transmit_complete(&mut self, token: u16, tx_buf: &[u8]) -> Result { + let len = self.xmit_queue.pop_used(token, &[tx_buf], &mut [])?; + Ok(len as usize) + } + + /// Submits a request to receive a buffer immediately without waiting for + /// the reception to complete. + /// + /// It will submit request to the VirtIO net device and return a token + /// identifying the position of the first descriptor in the chain. If there + /// are not enough descriptors to allocate, then it returns + /// [`Error::QueueFull`]. + /// + /// The caller can then call [`poll_receive`] with the returned token to + /// check whether the device has finished handling the request. Once it has, + /// the caller must call [`receive_complete`] with the same buffers before + /// reading the response. + /// + /// # Safety + /// + /// `rx_buf` is still borrowed by the underlying VirtIO net device even after + /// this method returns. Thus, it is the caller's responsibility to guarantee + /// that they are not accessed before the request is completed in order to + /// avoid data races. + /// + /// [`poll_receive`]: Self::poll_receive + /// [`receive_complete`]: Self::receive_complete + pub unsafe fn receive_begin(&mut self, rx_buf: &mut [u8]) -> Result { + Self::check_rx_buf_len(rx_buf)?; + let token = self.recv_queue.add(&[], &mut [rx_buf])?; + if self.recv_queue.should_notify() { + self.transport.notify(QUEUE_RECEIVE); + } + Ok(token) + } + + /// Fetches the token of the next completed reception request from the + /// used ring and returns it, without removing it from the used ring. If + /// there are no pending completed requests it returns [`None`]. + pub fn poll_receive(&mut self) -> Option { + self.recv_queue.peek_used() + } + + /// Completes a transmission operation which was started by [`receive_begin`]. + /// + /// After completion, the `rx_buf` will contain a header followed by the + /// received packet. It returns the length of the header and the length of + /// the packet. + /// + /// # Safety + /// + /// The same buffers must be passed in again as were passed to + /// [`receive_begin`] when it returned the token. + /// + /// [`receive_begin`]: Self::receive_begin + pub unsafe fn receive_complete( + &mut self, + token: u16, + rx_buf: &mut [u8], + ) -> Result<(usize, usize)> { + let len = self.recv_queue.pop_used(token, &[], &mut [rx_buf])? as usize; + let packet_len = len.checked_sub(NET_HDR_SIZE).ok_or(Error::IoError)?; + Ok((NET_HDR_SIZE, packet_len)) + } + + /// Transmits a packet to the network, and blocks until the request + /// completed. Returns number of bytes transmitted. + /// + /// The caller needs to fill the `tx_buf` with a header by calling + /// [`fill_buffer_header`] before transmission. + /// + /// [`fill_buffer_header`]: Self::fill_buffer_header + pub fn transmit_wait(&mut self, tx_buf: &[u8]) -> Result { + let token = unsafe { self.transmit_begin(tx_buf)? }; + while self.poll_transmit().is_none() { + core::hint::spin_loop(); + } + unsafe { self.transmit_complete(token, tx_buf) } + } + + /// Blocks and waits for a packet to be received. + /// + /// After completion, the `rx_buf` will contain a header followed by the + /// received packet. It returns the length of the header and the length of + /// the packet. + pub fn receive_wait(&mut self, rx_buf: &mut [u8]) -> Result<(usize, usize)> { + let token = unsafe { self.receive_begin(rx_buf)? }; + while self.poll_receive().is_none() { + core::hint::spin_loop(); + } + unsafe { self.receive_complete(token, rx_buf) } + } +} + +impl Drop for VirtIONetRaw { + fn drop(&mut self) { + // Clear any pointers pointing to DMA regions, so the device doesn't try to access them + // after they have been freed. + self.transport.queue_unset(QUEUE_RECEIVE); + self.transport.queue_unset(QUEUE_TRANSMIT); + } +} diff --git a/src/device/net/mod.rs b/src/device/net/mod.rs new file mode 100644 index 00000000..a0b513e4 --- /dev/null +++ b/src/device/net/mod.rs @@ -0,0 +1,149 @@ +//! Driver for VirtIO network devices. + +#[cfg(feature = "alloc")] +mod dev; +mod dev_raw; +#[cfg(feature = "alloc")] +mod net_buf; + +pub use self::dev_raw::VirtIONetRaw; +#[cfg(feature = "alloc")] +pub use self::{dev::VirtIONet, net_buf::NetBuffer}; + +use crate::volatile::ReadOnly; +use bitflags::bitflags; +use zerocopy::{AsBytes, FromBytes}; + +const QUEUE_RECEIVE: u16 = 0; +const QUEUE_TRANSMIT: u16 = 1; + +const MAX_BUFFER_LEN: usize = 65535; +const MIN_BUFFER_LEN: usize = 1526; +const NET_HDR_SIZE: usize = core::mem::size_of::(); + +bitflags! { + struct Features: u64 { + /// Device handles packets with partial checksum. + /// This "checksum offload" is a common feature on modern network cards. + const CSUM = 1 << 0; + /// Driver handles packets with partial checksum. + const GUEST_CSUM = 1 << 1; + /// Control channel offloads reconfiguration support. + const CTRL_GUEST_OFFLOADS = 1 << 2; + /// Device maximum MTU reporting is supported. + /// + /// If offered by the device, device advises driver about the value of + /// its maximum MTU. If negotiated, the driver uses mtu as the maximum + /// MTU value. + const MTU = 1 << 3; + /// Device has given MAC address. + const MAC = 1 << 5; + /// Device handles packets with any GSO type. (legacy) + const GSO = 1 << 6; + /// Driver can receive TSOv4. + const GUEST_TSO4 = 1 << 7; + /// Driver can receive TSOv6. + const GUEST_TSO6 = 1 << 8; + /// Driver can receive TSO with ECN. + const GUEST_ECN = 1 << 9; + /// Driver can receive UFO. + const GUEST_UFO = 1 << 10; + /// Device can receive TSOv4. + const HOST_TSO4 = 1 << 11; + /// Device can receive TSOv6. + const HOST_TSO6 = 1 << 12; + /// Device can receive TSO with ECN. + const HOST_ECN = 1 << 13; + /// Device can receive UFO. + const HOST_UFO = 1 << 14; + /// Driver can merge receive buffers. + const MRG_RXBUF = 1 << 15; + /// Configuration status field is available. + const STATUS = 1 << 16; + /// Control channel is available. + const CTRL_VQ = 1 << 17; + /// Control channel RX mode support. + const CTRL_RX = 1 << 18; + /// Control channel VLAN filtering. + const CTRL_VLAN = 1 << 19; + /// + const CTRL_RX_EXTRA = 1 << 20; + /// Driver can send gratuitous packets. + const GUEST_ANNOUNCE = 1 << 21; + /// Device supports multiqueue with automatic receive steering. + const MQ = 1 << 22; + /// Set MAC address through control channel. + const CTL_MAC_ADDR = 1 << 23; + + // device independent + const RING_INDIRECT_DESC = 1 << 28; + const RING_EVENT_IDX = 1 << 29; + const VERSION_1 = 1 << 32; // legacy + } +} + +bitflags! { + struct Status: u16 { + const LINK_UP = 1; + const ANNOUNCE = 2; + } +} + +bitflags! { + struct InterruptStatus : u32 { + const USED_RING_UPDATE = 1 << 0; + const CONFIGURATION_CHANGE = 1 << 1; + } +} + +#[repr(C)] +struct Config { + mac: ReadOnly, + status: ReadOnly, + max_virtqueue_pairs: ReadOnly, + mtu: ReadOnly, +} + +type EthernetAddress = [u8; 6]; + +/// A header that precedes all network packets. +/// +/// In VirtIO 5.1.6 Device Operation: +/// +/// Packets are transmitted by placing them in the transmitq1. . .transmitqN, +/// and buffers for incoming packets are placed in the receiveq1. . .receiveqN. +/// In each case, the packet itself is preceded by a header. +#[repr(C)] +#[derive(AsBytes, Debug, Default, FromBytes)] +pub struct VirtioNetHdr { + flags: Flags, + gso_type: GsoType, + hdr_len: u16, // cannot rely on this + gso_size: u16, + csum_start: u16, + csum_offset: u16, + // num_buffers: u16, // only available when the feature MRG_RXBUF is negotiated. + // payload starts from here +} + +bitflags! { + #[repr(transparent)] + #[derive(AsBytes, Default, FromBytes)] + struct Flags: u8 { + const NEEDS_CSUM = 1; + const DATA_VALID = 2; + const RSC_INFO = 4; + } +} + +#[repr(transparent)] +#[derive(AsBytes, Debug, Copy, Clone, Default, Eq, FromBytes, PartialEq)] +struct GsoType(u8); + +impl GsoType { + const NONE: GsoType = GsoType(0); + const TCPV4: GsoType = GsoType(1); + const UDP: GsoType = GsoType(3); + const TCPV6: GsoType = GsoType(4); + const ECN: GsoType = GsoType(0x80); +} diff --git a/src/device/net/net_buf.rs b/src/device/net/net_buf.rs new file mode 100644 index 00000000..739fbea2 --- /dev/null +++ b/src/device/net/net_buf.rs @@ -0,0 +1,55 @@ +use super::{VirtioNetHdr, NET_HDR_SIZE}; +use alloc::{vec, vec::Vec}; +use zerocopy::AsBytes; + +/// A buffer used for receiving and transmitting. +pub struct NetBuffer { + buf: Vec, // for alignment + packet_len: usize, +} + +impl NetBuffer { + /// Allocates a new buffer with length `buf_len`. + pub(crate) fn new(buf_len: usize) -> Self { + Self { + buf: vec![0; (buf_len - 1) / core::mem::size_of::() + 1], + packet_len: 0, + } + } + + /// Set the network packet length. + pub(crate) fn set_packet_len(&mut self, packet_len: usize) { + self.packet_len = packet_len + } + + /// Returns the network packet length (witout header). + pub const fn packet_len(&self) -> usize { + self.packet_len + } + + /// Returns all data in the buffer, including both the header and the packet. + pub fn as_bytes(&self) -> &[u8] { + self.buf.as_bytes() + } + + /// Returns all data in the buffer with the mutable reference, + /// including both the header and the packet. + pub fn as_bytes_mut(&mut self) -> &mut [u8] { + self.buf.as_bytes_mut() + } + + /// Returns the reference of the header. + pub fn header(&self) -> &VirtioNetHdr { + unsafe { &*(self.buf.as_ptr() as *const VirtioNetHdr) } + } + + /// Returns the network packet as a slice. + pub fn packet(&self) -> &[u8] { + &self.buf.as_bytes()[NET_HDR_SIZE..NET_HDR_SIZE + self.packet_len] + } + + /// Returns the network packet as a mutable slice. + pub fn packet_mut(&mut self) -> &mut [u8] { + &mut self.buf.as_bytes_mut()[NET_HDR_SIZE..NET_HDR_SIZE + self.packet_len] + } +} From dd88264b7e047841fb975d3292f5b9c27802d84b Mon Sep 17 00:00:00 2001 From: hky1999 <976929993@qq.com> Date: Sun, 24 Sep 2023 18:51:15 +0800 Subject: [PATCH 02/14] wip: add used buffer notification suppression but do not work --- src/device/net/dev_raw.rs | 14 +++++++++ src/queue.rs | 66 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 80 insertions(+) diff --git a/src/device/net/dev_raw.rs b/src/device/net/dev_raw.rs index 620daccf..7358744e 100644 --- a/src/device/net/dev_raw.rs +++ b/src/device/net/dev_raw.rs @@ -62,6 +62,20 @@ impl VirtIONetRaw EthernetAddress { self.mac diff --git a/src/queue.rs b/src/queue.rs index cc103256..34585056 100644 --- a/src/queue.rs +++ b/src/queue.rs @@ -316,6 +316,41 @@ impl VirtQueue { unsafe { self.pop_used(token, inputs, outputs) } } + /// Advise the device that notifications are needed. + /// + /// See Virtio v1.1 2.6.7 Used Buffer Notification Suppression + pub fn enable_dev_notify(&self) { + // Safe because self.used points to a valid, aligned, initialised, dereferenceable, readable + // instance of UsedRing. + log::debug!( + "enable_dev_notify event_idx {} avail flags {}", + self.event_idx, + unsafe { (*self.avail.as_ptr()).flags } + ); + if !self.event_idx { + unsafe { (*self.avail.as_ptr()).flags = 0x0 } + } + } + + /// Advise the device that notifications are not needed. + /// + /// See Virtio v1.1 2.6.7 Used Buffer Notification Suppression + pub fn disable_dev_notify(&self) { + log::debug!( + "disable_dev_notify event_idx {} avail flags {}", + self.event_idx, + unsafe { (*self.avail.as_ptr()).flags } + ); + if !self.event_idx { + unsafe { (*self.avail.as_ptr()).flags = 0x1 } + } + log::debug!( + "disable_dev_notify event_idx {} avail flags {}", + self.event_idx, + unsafe { (*self.avail.as_ptr()).flags } + ); + } + /// Returns whether the driver should notify the device after adding a new buffer to the /// virtqueue. /// @@ -1117,6 +1152,37 @@ mod tests { } } + /// Tests that the queue advises the device that notifications are needed. + #[test] + fn set_dev_notify() { + let mut config_space = (); + let state = Arc::new(Mutex::new(State { + queues: vec![QueueStatus::default()], + ..Default::default() + })); + let mut transport = FakeTransport { + device_type: DeviceType::Block, + max_queue_size: 4, + device_features: 0, + config_space: NonNull::from(&mut config_space), + state: state.clone(), + }; + let queue = VirtQueue::::new(&mut transport, 0, false, false).unwrap(); + + // Check that the avail ring's flag is zero by default. + assert_eq!(unsafe { (*queue.avail.as_ptr()).flags }, 0x0); + + queue.disable_dev_notify(); + + // Check that the avail ring's flag is 1 after `disable_dev_notify`. + assert_eq!(unsafe { (*queue.avail.as_ptr()).flags }, 0x1); + + queue.enable_dev_notify(); + + // Check that the avail ring's flag is 0 after `enable_dev_notify`. + assert_eq!(unsafe { (*queue.avail.as_ptr()).flags }, 0x0); + } + /// Tests that the queue notifies the device about added buffers, if it hasn't suppressed /// notifications. #[test] From 3725aa8b698e8f7418991f6b193abd341ba0f9b0 Mon Sep 17 00:00:00 2001 From: hky1999 <976929993@qq.com> Date: Sun, 24 Sep 2023 20:04:57 +0800 Subject: [PATCH 03/14] feat: disable_interrupts by disable_dev_notify of both xmit_queue and recv_queue --- src/device/net/dev_raw.rs | 6 ++---- src/queue.rs | 35 +++++++++++++---------------------- 2 files changed, 15 insertions(+), 26 deletions(-) diff --git a/src/device/net/dev_raw.rs b/src/device/net/dev_raw.rs index 7358744e..bfec9ec6 100644 --- a/src/device/net/dev_raw.rs +++ b/src/device/net/dev_raw.rs @@ -64,15 +64,13 @@ impl VirtIONetRaw VirtQueue { /// Advise the device that notifications are needed. /// /// See Virtio v1.1 2.6.7 Used Buffer Notification Suppression - pub fn enable_dev_notify(&self) { - // Safe because self.used points to a valid, aligned, initialised, dereferenceable, readable - // instance of UsedRing. - log::debug!( - "enable_dev_notify event_idx {} avail flags {}", - self.event_idx, - unsafe { (*self.avail.as_ptr()).flags } - ); + pub fn enable_dev_notify(&mut self) { if !self.event_idx { - unsafe { (*self.avail.as_ptr()).flags = 0x0 } + // Safe because self.avail points to a valid, aligned, initialised, dereferenceable, readable + // instance of AvailRing. + unsafe { (*self.avail.as_ptr()).flags = 0x0000 } } + // Write barrier so that device can see change to available index after this method returns. + fence(Ordering::SeqCst); } /// Advise the device that notifications are not needed. /// /// See Virtio v1.1 2.6.7 Used Buffer Notification Suppression - pub fn disable_dev_notify(&self) { - log::debug!( - "disable_dev_notify event_idx {} avail flags {}", - self.event_idx, - unsafe { (*self.avail.as_ptr()).flags } - ); + pub fn disable_dev_notify(&mut self) { if !self.event_idx { - unsafe { (*self.avail.as_ptr()).flags = 0x1 } + // Safe because self.avail points to a valid, aligned, initialised, dereferenceable, readable + // instance of AvailRing. + unsafe { (*self.avail.as_ptr()).flags = 0x0001 } } - log::debug!( - "disable_dev_notify event_idx {} avail flags {}", - self.event_idx, - unsafe { (*self.avail.as_ptr()).flags } - ); + // Write barrier so that device can see change to available index after this method returns. + fence(Ordering::SeqCst); } /// Returns whether the driver should notify the device after adding a new buffer to the @@ -1167,7 +1158,7 @@ mod tests { config_space: NonNull::from(&mut config_space), state: state.clone(), }; - let queue = VirtQueue::::new(&mut transport, 0, false, false).unwrap(); + let mut queue = VirtQueue::::new(&mut transport, 0, false, false).unwrap(); // Check that the avail ring's flag is zero by default. assert_eq!(unsafe { (*queue.avail.as_ptr()).flags }, 0x0); From 80f13fcd6530b6490cdda8eb53c23c5c9791302e Mon Sep 17 00:00:00 2001 From: hky1999 <976929993@qq.com> Date: Mon, 25 Sep 2023 17:44:35 +0800 Subject: [PATCH 04/14] feat: rebase new-netdev to master and support netdev used buffer notification suppression --- src/device/mod.rs | 2 + src/device/net.rs | 413 -------------------------------------- src/device/net/dev.rs | 65 ++++-- src/device/net/dev_raw.rs | 71 +++++-- src/device/net/mod.rs | 26 ++- src/device/net/net_buf.rs | 42 +++- 6 files changed, 146 insertions(+), 473 deletions(-) delete mode 100644 src/device/net.rs diff --git a/src/device/mod.rs b/src/device/mod.rs index e7929707..d8e6389d 100644 --- a/src/device/mod.rs +++ b/src/device/mod.rs @@ -7,7 +7,9 @@ pub mod console; pub mod gpu; #[cfg(feature = "alloc")] pub mod input; + pub mod net; + pub mod socket; pub(crate) mod common; diff --git a/src/device/net.rs b/src/device/net.rs deleted file mode 100644 index b32cd725..00000000 --- a/src/device/net.rs +++ /dev/null @@ -1,413 +0,0 @@ -//! Driver for VirtIO network devices. - -use crate::hal::Hal; -use crate::queue::VirtQueue; -use crate::transport::Transport; -use crate::volatile::{volread, ReadOnly}; -use crate::{Error, Result}; -use alloc::{vec, vec::Vec}; -use bitflags::bitflags; -use core::{convert::TryInto, mem::size_of}; -use log::{debug, warn}; -use zerocopy::{AsBytes, FromBytes, FromZeroes}; - -const MAX_BUFFER_LEN: usize = 65535; -const MIN_BUFFER_LEN: usize = 1526; -const NET_HDR_SIZE: usize = size_of::(); - -/// A buffer used for transmitting. -pub struct TxBuffer(Vec); - -/// A buffer used for receiving. -pub struct RxBuffer { - buf: Vec, // for alignment - packet_len: usize, - idx: u16, -} - -impl TxBuffer { - /// Constructs the buffer from the given slice. - pub fn from(buf: &[u8]) -> Self { - Self(Vec::from(buf)) - } - - /// Returns the network packet length. - pub fn packet_len(&self) -> usize { - self.0.len() - } - - /// Returns the network packet as a slice. - pub fn packet(&self) -> &[u8] { - self.0.as_slice() - } - - /// Returns the network packet as a mutable slice. - pub fn packet_mut(&mut self) -> &mut [u8] { - self.0.as_mut_slice() - } -} - -impl RxBuffer { - /// Allocates a new buffer with length `buf_len`. - fn new(idx: usize, buf_len: usize) -> Self { - Self { - buf: vec![0; buf_len / size_of::()], - packet_len: 0, - idx: idx.try_into().unwrap(), - } - } - - /// Set the network packet length. - fn set_packet_len(&mut self, packet_len: usize) { - self.packet_len = packet_len - } - - /// Returns the network packet length (witout header). - pub const fn packet_len(&self) -> usize { - self.packet_len - } - - /// Returns all data in the buffer, including both the header and the packet. - pub fn as_bytes(&self) -> &[u8] { - self.buf.as_bytes() - } - - /// Returns all data in the buffer with the mutable reference, - /// including both the header and the packet. - pub fn as_bytes_mut(&mut self) -> &mut [u8] { - self.buf.as_bytes_mut() - } - - /// Returns the reference of the header. - pub fn header(&self) -> &VirtioNetHdr { - unsafe { &*(self.buf.as_ptr() as *const VirtioNetHdr) } - } - - /// Returns the network packet as a slice. - pub fn packet(&self) -> &[u8] { - &self.buf.as_bytes()[NET_HDR_SIZE..NET_HDR_SIZE + self.packet_len] - } - - /// Returns the network packet as a mutable slice. - pub fn packet_mut(&mut self) -> &mut [u8] { - &mut self.buf.as_bytes_mut()[NET_HDR_SIZE..NET_HDR_SIZE + self.packet_len] - } -} - -/// The virtio network device is a virtual ethernet card. -/// -/// It has enhanced rapidly and demonstrates clearly how support for new -/// features are added to an existing device. -/// Empty buffers are placed in one virtqueue for receiving packets, and -/// outgoing packets are enqueued into another for transmission in that order. -/// A third command queue is used to control advanced filtering features. -pub struct VirtIONet { - transport: T, - mac: EthernetAddress, - recv_queue: VirtQueue, - send_queue: VirtQueue, - rx_buffers: [Option; QUEUE_SIZE], -} - -impl VirtIONet { - /// Create a new VirtIO-Net driver. - pub fn new(mut transport: T, buf_len: usize) -> Result { - let negotiated_features = transport.begin_init(SUPPORTED_FEATURES); - // read configuration space - let config = transport.config_space::()?; - let mac; - // Safe because config points to a valid MMIO region for the config space. - unsafe { - mac = volread!(config, mac); - debug!( - "Got MAC={:02x?}, status={:?}", - mac, - volread!(config, status) - ); - } - - if !(MIN_BUFFER_LEN..=MAX_BUFFER_LEN).contains(&buf_len) { - warn!( - "Receive buffer len {} is not in range [{}, {}]", - buf_len, MIN_BUFFER_LEN, MAX_BUFFER_LEN - ); - return Err(Error::InvalidParam); - } - - let send_queue = VirtQueue::new( - &mut transport, - QUEUE_TRANSMIT, - false, - negotiated_features.contains(Features::RING_EVENT_IDX), - )?; - let mut recv_queue = VirtQueue::new( - &mut transport, - QUEUE_RECEIVE, - false, - negotiated_features.contains(Features::RING_EVENT_IDX), - )?; - - const NONE_BUF: Option = None; - let mut rx_buffers = [NONE_BUF; QUEUE_SIZE]; - for (i, rx_buf_place) in rx_buffers.iter_mut().enumerate() { - let mut rx_buf = RxBuffer::new(i, buf_len); - // Safe because the buffer lives as long as the queue. - let token = unsafe { recv_queue.add(&[], &mut [rx_buf.as_bytes_mut()])? }; - assert_eq!(token, i as u16); - *rx_buf_place = Some(rx_buf); - } - - if recv_queue.should_notify() { - transport.notify(QUEUE_RECEIVE); - } - - transport.finish_init(); - - Ok(VirtIONet { - transport, - mac, - recv_queue, - send_queue, - rx_buffers, - }) - } - - /// Acknowledge interrupt. - pub fn ack_interrupt(&mut self) -> bool { - self.transport.ack_interrupt() - } - - /// Get MAC address. - pub fn mac_address(&self) -> EthernetAddress { - self.mac - } - - /// Whether can send packet. - pub fn can_send(&self) -> bool { - self.send_queue.available_desc() >= 2 - } - - /// Whether can receive packet. - pub fn can_recv(&self) -> bool { - self.recv_queue.can_pop() - } - - /// Receives a [`RxBuffer`] from network. If currently no data, returns an - /// error with type [`Error::NotReady`]. - /// - /// It will try to pop a buffer that completed data reception in the - /// NIC queue. - pub fn receive(&mut self) -> Result { - if let Some(token) = self.recv_queue.peek_used() { - let mut rx_buf = self.rx_buffers[token as usize] - .take() - .ok_or(Error::WrongToken)?; - if token != rx_buf.idx { - return Err(Error::WrongToken); - } - - // Safe because `token` == `rx_buf.idx`, we are passing the same - // buffer as we passed to `VirtQueue::add` and it is still valid. - let len = unsafe { - self.recv_queue - .pop_used(token, &[], &mut [rx_buf.as_bytes_mut()])? - } as usize; - rx_buf.set_packet_len(len.checked_sub(NET_HDR_SIZE).ok_or(Error::IoError)?); - Ok(rx_buf) - } else { - Err(Error::NotReady) - } - } - - /// Gives back the ownership of `rx_buf`, and recycles it for next use. - /// - /// It will add the buffer back to the NIC queue. - pub fn recycle_rx_buffer(&mut self, mut rx_buf: RxBuffer) -> Result { - // Safe because we take the ownership of `rx_buf` back to `rx_buffers`, - // it lives as long as the queue. - let new_token = unsafe { self.recv_queue.add(&[], &mut [rx_buf.as_bytes_mut()]) }?; - // `rx_buffers[new_token]` is expected to be `None` since it was taken - // away at `Self::receive()` and has not been added back. - if self.rx_buffers[new_token as usize].is_some() { - return Err(Error::WrongToken); - } - rx_buf.idx = new_token; - self.rx_buffers[new_token as usize] = Some(rx_buf); - if self.recv_queue.should_notify() { - self.transport.notify(QUEUE_RECEIVE); - } - Ok(()) - } - - /// Allocate a new buffer for transmitting. - pub fn new_tx_buffer(&self, buf_len: usize) -> TxBuffer { - TxBuffer(vec![0; buf_len]) - } - - /// Sends a [`TxBuffer`] to the network, and blocks until the request - /// completed. - pub fn send(&mut self, tx_buf: TxBuffer) -> Result { - let header = VirtioNetHdr::default(); - if tx_buf.packet_len() == 0 { - // Special case sending an empty packet, to avoid adding an empty buffer to the - // virtqueue. - self.send_queue.add_notify_wait_pop( - &[header.as_bytes()], - &mut [], - &mut self.transport, - )?; - } else { - self.send_queue.add_notify_wait_pop( - &[header.as_bytes(), tx_buf.packet()], - &mut [], - &mut self.transport, - )?; - } - Ok(()) - } -} - -impl Drop for VirtIONet { - fn drop(&mut self) { - // Clear any pointers pointing to DMA regions, so the device doesn't try to access them - // after they have been freed. - self.transport.queue_unset(QUEUE_RECEIVE); - self.transport.queue_unset(QUEUE_TRANSMIT); - } -} - -bitflags! { - #[derive(Copy, Clone, Debug, Default, Eq, PartialEq)] - struct Features: u64 { - /// Device handles packets with partial checksum. - /// This "checksum offload" is a common feature on modern network cards. - const CSUM = 1 << 0; - /// Driver handles packets with partial checksum. - const GUEST_CSUM = 1 << 1; - /// Control channel offloads reconfiguration support. - const CTRL_GUEST_OFFLOADS = 1 << 2; - /// Device maximum MTU reporting is supported. - /// - /// If offered by the device, device advises driver about the value of - /// its maximum MTU. If negotiated, the driver uses mtu as the maximum - /// MTU value. - const MTU = 1 << 3; - /// Device has given MAC address. - const MAC = 1 << 5; - /// Device handles packets with any GSO type. (legacy) - const GSO = 1 << 6; - /// Driver can receive TSOv4. - const GUEST_TSO4 = 1 << 7; - /// Driver can receive TSOv6. - const GUEST_TSO6 = 1 << 8; - /// Driver can receive TSO with ECN. - const GUEST_ECN = 1 << 9; - /// Driver can receive UFO. - const GUEST_UFO = 1 << 10; - /// Device can receive TSOv4. - const HOST_TSO4 = 1 << 11; - /// Device can receive TSOv6. - const HOST_TSO6 = 1 << 12; - /// Device can receive TSO with ECN. - const HOST_ECN = 1 << 13; - /// Device can receive UFO. - const HOST_UFO = 1 << 14; - /// Driver can merge receive buffers. - const MRG_RXBUF = 1 << 15; - /// Configuration status field is available. - const STATUS = 1 << 16; - /// Control channel is available. - const CTRL_VQ = 1 << 17; - /// Control channel RX mode support. - const CTRL_RX = 1 << 18; - /// Control channel VLAN filtering. - const CTRL_VLAN = 1 << 19; - /// - const CTRL_RX_EXTRA = 1 << 20; - /// Driver can send gratuitous packets. - const GUEST_ANNOUNCE = 1 << 21; - /// Device supports multiqueue with automatic receive steering. - const MQ = 1 << 22; - /// Set MAC address through control channel. - const CTL_MAC_ADDR = 1 << 23; - - // device independent - const RING_INDIRECT_DESC = 1 << 28; - const RING_EVENT_IDX = 1 << 29; - const VERSION_1 = 1 << 32; // legacy - } -} - -bitflags! { - #[derive(Copy, Clone, Debug, Default, Eq, PartialEq)] - struct Status: u16 { - const LINK_UP = 1; - const ANNOUNCE = 2; - } -} - -bitflags! { - #[derive(Copy, Clone, Debug, Default, Eq, PartialEq)] - struct InterruptStatus : u32 { - const USED_RING_UPDATE = 1 << 0; - const CONFIGURATION_CHANGE = 1 << 1; - } -} - -#[repr(C)] -struct Config { - mac: ReadOnly, - status: ReadOnly, - max_virtqueue_pairs: ReadOnly, - mtu: ReadOnly, -} - -type EthernetAddress = [u8; 6]; - -/// VirtIO 5.1.6 Device Operation: -/// -/// Packets are transmitted by placing them in the transmitq1. . .transmitqN, -/// and buffers for incoming packets are placed in the receiveq1. . .receiveqN. -/// In each case, the packet itself is preceded by a header. -#[repr(C)] -#[derive(AsBytes, Debug, Default, FromBytes, FromZeroes)] -pub struct VirtioNetHdr { - flags: Flags, - gso_type: GsoType, - hdr_len: u16, // cannot rely on this - gso_size: u16, - csum_start: u16, - csum_offset: u16, - // num_buffers: u16, // only available when the feature MRG_RXBUF is negotiated. - // payload starts from here -} - -#[derive(AsBytes, Copy, Clone, Debug, Default, Eq, FromBytes, FromZeroes, PartialEq)] -#[repr(transparent)] -struct Flags(u8); - -bitflags! { - impl Flags: u8 { - const NEEDS_CSUM = 1; - const DATA_VALID = 2; - const RSC_INFO = 4; - } -} - -#[repr(transparent)] -#[derive(AsBytes, Debug, Copy, Clone, Default, Eq, FromBytes, FromZeroes, PartialEq)] -struct GsoType(u8); - -impl GsoType { - const NONE: GsoType = GsoType(0); - const TCPV4: GsoType = GsoType(1); - const UDP: GsoType = GsoType(3); - const TCPV6: GsoType = GsoType(4); - const ECN: GsoType = GsoType(0x80); -} - -const QUEUE_RECEIVE: u16 = 0; -const QUEUE_TRANSMIT: u16 = 1; -const SUPPORTED_FEATURES: Features = Features::MAC - .union(Features::STATUS) - .union(Features::RING_EVENT_IDX); diff --git a/src/device/net/dev.rs b/src/device/net/dev.rs index d24b9411..388660ed 100644 --- a/src/device/net/dev.rs +++ b/src/device/net/dev.rs @@ -1,5 +1,7 @@ -use super::net_buf::NetBuffer; -use super::{EthernetAddress, VirtIONetRaw, NET_HDR_SIZE}; +use alloc::vec; + +use super::net_buf::{RxBuffer, TxBuffer}; +use super::{EthernetAddress, VirtIONetRaw}; use crate::{hal::Hal, transport::Transport, Error, Result}; /// Driver for a VirtIO block device. @@ -7,9 +9,17 @@ use crate::{hal::Hal, transport::Transport, Error, Result}; /// Unlike [`VirtIONetRaw`], it uses [`NetBuffer`]s for transmission and /// reception rather than the raw slices. On initialization, it pre-allocates /// all receive buffers and puts them all in the receive queue. +/// +/// The virtio network device is a virtual ethernet card. +/// +/// It has enhanced rapidly and demonstrates clearly how support for new +/// features are added to an existing device. +/// Empty buffers are placed in one virtqueue for receiving packets, and +/// outgoing packets are enqueued into another for transmission in that order. +/// A third command queue is used to control advanced filtering features. pub struct VirtIONet { inner: VirtIONetRaw, - rx_buffers: [Option; QUEUE_SIZE], + rx_buffers: [Option; QUEUE_SIZE], } impl VirtIONet { @@ -17,10 +27,10 @@ impl VirtIONet pub fn new(transport: T, buf_len: usize) -> Result { let mut inner = VirtIONetRaw::new(transport)?; - const NONE_BUF: Option = None; + const NONE_BUF: Option = None; let mut rx_buffers = [NONE_BUF; QUEUE_SIZE]; for (i, rx_buf_place) in rx_buffers.iter_mut().enumerate() { - let mut rx_buf = NetBuffer::new(buf_len); + let mut rx_buf = RxBuffer::new(i, buf_len); // Safe because the buffer lives as long as the queue. let token = unsafe { inner.receive_begin(rx_buf.as_bytes_mut())? }; assert_eq!(token, i as u16); @@ -35,31 +45,44 @@ impl VirtIONet self.inner.ack_interrupt() } + /// Disable interrupts. + pub fn disable_interrupts(&mut self) { + self.inner.disable_interrupts() + } + + /// Enable interrupts. + pub fn enable_interrupts(&mut self) { + self.inner.disable_interrupts() + } + /// Get MAC address. pub fn mac_address(&self) -> EthernetAddress { self.inner.mac_address() } - /// Whether can transmit packet. - pub fn can_transmit(&self) -> bool { - self.inner.can_transmit() + /// Whether can send packet. + pub fn can_send(&self) -> bool { + self.inner.can_send() } /// Whether can receive packet. - pub fn can_receive(&self) -> bool { - self.inner.can_receive() + pub fn can_recv(&self) -> bool { + self.inner.can_recv() } - /// Receives a [`NetBuffer`] from network. If currently no data, returns an + /// Receives a [`RxBuffer`] from network. If currently no data, returns an /// error with type [`Error::NotReady`]. /// /// It will try to pop a buffer that completed data reception in the /// NIC queue. - pub fn receive(&mut self) -> Result { + pub fn receive(&mut self) -> Result { if let Some(token) = self.inner.poll_receive() { let mut rx_buf = self.rx_buffers[token as usize] .take() .ok_or(Error::WrongToken)?; + if token != rx_buf.idx { + return Err(Error::WrongToken); + } // Safe because `token` == `rx_buf.idx`, we are passing the same // buffer as we passed to `VirtQueue::add` and it is still valid. @@ -75,7 +98,7 @@ impl VirtIONet /// Gives back the ownership of `rx_buf`, and recycles it for next use. /// /// It will add the buffer back to the NIC queue. - pub fn recycle_rx_buffer(&mut self, mut rx_buf: NetBuffer) -> Result { + pub fn recycle_rx_buffer(&mut self, mut rx_buf: RxBuffer) -> Result { // Safe because we take the ownership of `rx_buf` back to `rx_buffers`, // it lives as long as the queue. let new_token = unsafe { self.inner.receive_begin(rx_buf.as_bytes_mut()) }?; @@ -84,21 +107,19 @@ impl VirtIONet if self.rx_buffers[new_token as usize].is_some() { return Err(Error::WrongToken); } + rx_buf.idx = new_token; self.rx_buffers[new_token as usize] = Some(rx_buf); Ok(()) } /// Allocate a new buffer for transmitting. - pub fn new_tx_buffer(&self, packet_len: usize) -> Result { - let mut tx_buf = NetBuffer::new(NET_HDR_SIZE + packet_len); - self.inner.fill_buffer_header(tx_buf.as_bytes_mut())?; - tx_buf.set_packet_len(packet_len); - Ok(tx_buf) + pub fn new_tx_buffer(&self, buf_len: usize) -> TxBuffer { + TxBuffer(vec![0; buf_len]) } - /// Sends a [`NetBuffer`] to the network, and blocks until the request - /// completed. Returns number of bytes transmitted. - pub fn transmit_wait(&mut self, tx_buf: NetBuffer) -> Result { - self.inner.transmit_wait(tx_buf.as_bytes()) + /// Sends a [`TxBuffer`] to the network, and blocks until the request + /// completed. + pub fn send(&mut self, tx_buf: TxBuffer) -> Result { + self.inner.send(tx_buf) } } diff --git a/src/device/net/dev_raw.rs b/src/device/net/dev_raw.rs index bfec9ec6..9852ad40 100644 --- a/src/device/net/dev_raw.rs +++ b/src/device/net/dev_raw.rs @@ -1,3 +1,4 @@ +use super::TxBuffer; use super::{Config, EthernetAddress, Features, VirtioNetHdr}; use super::{MIN_BUFFER_LEN, NET_HDR_SIZE, QUEUE_RECEIVE, QUEUE_TRANSMIT}; use crate::hal::Hal; @@ -20,18 +21,14 @@ pub struct VirtIONetRaw { transport: T, mac: EthernetAddress, recv_queue: VirtQueue, - xmit_queue: VirtQueue, + send_queue: VirtQueue, } impl VirtIONetRaw { /// Create a new VirtIO-Net driver. pub fn new(mut transport: T) -> Result { - transport.begin_init(|features| { - let features = Features::from_bits_truncate(features); - info!("Device features {:?}", features); - let supported_features = Features::MAC | Features::STATUS; - (features & supported_features).bits() - }); + let negotiated_features = transport.begin_init(Features::MAC | Features::STATUS); + info!("negotiated_features {:?}", negotiated_features); // read configuration space let config = transport.config_space::()?; let mac; @@ -44,16 +41,26 @@ impl VirtIONetRaw VirtIONetRaw VirtIONetRaw bool { - self.xmit_queue.available_desc() >= 1 + /// Whether can send packet. + pub fn can_send(&self) -> bool { + self.send_queue.available_desc() >= 1 } /// Whether can receive packet. - pub fn can_receive(&self) -> bool { + pub fn can_recv(&self) -> bool { self.recv_queue.can_pop() } @@ -147,8 +154,8 @@ impl VirtIONetRaw Result { Self::check_tx_buf_len(tx_buf)?; - let token = self.xmit_queue.add(&[tx_buf], &mut [])?; - if self.xmit_queue.should_notify() { + let token = self.send_queue.add(&[tx_buf], &mut [])?; + if self.send_queue.should_notify() { self.transport.notify(QUEUE_TRANSMIT); } Ok(token) @@ -158,7 +165,7 @@ impl VirtIONetRaw Option { - self.xmit_queue.peek_used() + self.send_queue.peek_used() } /// Completes a transmission operation which was started by [`transmit_begin`]. @@ -171,7 +178,7 @@ impl VirtIONetRaw Result { - let len = self.xmit_queue.pop_used(token, &[tx_buf], &mut [])?; + let len = self.send_queue.pop_used(token, &[tx_buf], &mut [])?; Ok(len as usize) } @@ -235,6 +242,28 @@ impl VirtIONetRaw Result { + let header = VirtioNetHdr::default(); + if tx_buf.packet_len() == 0 { + // Special case sending an empty packet, to avoid adding an empty buffer to the + // virtqueue. + self.send_queue.add_notify_wait_pop( + &[header.as_bytes()], + &mut [], + &mut self.transport, + )?; + } else { + self.send_queue.add_notify_wait_pop( + &[header.as_bytes(), tx_buf.packet()], + &mut [], + &mut self.transport, + )?; + } + Ok(()) + } + /// Transmits a packet to the network, and blocks until the request /// completed. Returns number of bytes transmitted. /// diff --git a/src/device/net/mod.rs b/src/device/net/mod.rs index a0b513e4..05a8f538 100644 --- a/src/device/net/mod.rs +++ b/src/device/net/mod.rs @@ -8,20 +8,18 @@ mod net_buf; pub use self::dev_raw::VirtIONetRaw; #[cfg(feature = "alloc")] -pub use self::{dev::VirtIONet, net_buf::NetBuffer}; +pub use self::{dev::VirtIONet, net_buf::RxBuffer, net_buf::TxBuffer}; use crate::volatile::ReadOnly; use bitflags::bitflags; use zerocopy::{AsBytes, FromBytes}; -const QUEUE_RECEIVE: u16 = 0; -const QUEUE_TRANSMIT: u16 = 1; - const MAX_BUFFER_LEN: usize = 65535; const MIN_BUFFER_LEN: usize = 1526; const NET_HDR_SIZE: usize = core::mem::size_of::(); bitflags! { + #[derive(Copy, Clone, Debug, Default, Eq, PartialEq)] struct Features: u64 { /// Device handles packets with partial checksum. /// This "checksum offload" is a common feature on modern network cards. @@ -83,6 +81,7 @@ bitflags! { } bitflags! { + #[derive(Copy, Clone, Debug, Default, Eq, PartialEq)] struct Status: u16 { const LINK_UP = 1; const ANNOUNCE = 2; @@ -90,6 +89,7 @@ bitflags! { } bitflags! { + #[derive(Copy, Clone, Debug, Default, Eq, PartialEq)] struct InterruptStatus : u32 { const USED_RING_UPDATE = 1 << 0; const CONFIGURATION_CHANGE = 1 << 1; @@ -106,9 +106,7 @@ struct Config { type EthernetAddress = [u8; 6]; -/// A header that precedes all network packets. -/// -/// In VirtIO 5.1.6 Device Operation: +/// VirtIO 5.1.6 Device Operation: /// /// Packets are transmitted by placing them in the transmitq1. . .transmitqN, /// and buffers for incoming packets are placed in the receiveq1. . .receiveqN. @@ -126,10 +124,12 @@ pub struct VirtioNetHdr { // payload starts from here } +#[derive(AsBytes, Copy, Clone, Debug, Default, Eq, FromBytes, PartialEq)] +#[repr(transparent)] +struct Flags(u8); + bitflags! { - #[repr(transparent)] - #[derive(AsBytes, Default, FromBytes)] - struct Flags: u8 { + impl Flags: u8 { const NEEDS_CSUM = 1; const DATA_VALID = 2; const RSC_INFO = 4; @@ -147,3 +147,9 @@ impl GsoType { const TCPV6: GsoType = GsoType(4); const ECN: GsoType = GsoType(0x80); } + +const QUEUE_RECEIVE: u16 = 0; +const QUEUE_TRANSMIT: u16 = 1; +const SUPPORTED_FEATURES: Features = Features::MAC + .union(Features::STATUS) + .union(Features::RING_EVENT_IDX); diff --git a/src/device/net/net_buf.rs b/src/device/net/net_buf.rs index 739fbea2..8b4947bf 100644 --- a/src/device/net/net_buf.rs +++ b/src/device/net/net_buf.rs @@ -1,19 +1,47 @@ use super::{VirtioNetHdr, NET_HDR_SIZE}; use alloc::{vec, vec::Vec}; +use core::{convert::TryInto, mem::size_of}; use zerocopy::AsBytes; -/// A buffer used for receiving and transmitting. -pub struct NetBuffer { - buf: Vec, // for alignment - packet_len: usize, +/// A buffer used for transmitting. +pub struct TxBuffer(pub(crate) Vec); + +/// A buffer used for receiving. +pub struct RxBuffer { + pub(crate) buf: Vec, // for alignment + pub(crate) packet_len: usize, + pub(crate) idx: u16, +} + +impl TxBuffer { + /// Constructs the buffer from the given slice. + pub fn from(buf: &[u8]) -> Self { + Self(Vec::from(buf)) + } + + /// Returns the network packet length. + pub fn packet_len(&self) -> usize { + self.0.len() + } + + /// Returns the network packet as a slice. + pub fn packet(&self) -> &[u8] { + self.0.as_slice() + } + + /// Returns the network packet as a mutable slice. + pub fn packet_mut(&mut self) -> &mut [u8] { + self.0.as_mut_slice() + } } -impl NetBuffer { +impl RxBuffer { /// Allocates a new buffer with length `buf_len`. - pub(crate) fn new(buf_len: usize) -> Self { + pub(crate) fn new(idx: usize, buf_len: usize) -> Self { Self { - buf: vec![0; (buf_len - 1) / core::mem::size_of::() + 1], + buf: vec![0; buf_len / size_of::()], packet_len: 0, + idx: idx.try_into().unwrap(), } } From d0cfe7f0e73e0bb69222bdc4bb5d279913043c3f Mon Sep 17 00:00:00 2001 From: hky1999 <976929993@qq.com> Date: Tue, 26 Sep 2023 17:25:14 +0800 Subject: [PATCH 05/14] fix: cargo fmt --- src/device/net/dev.rs | 4 ++-- src/device/net/dev_raw.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/device/net/dev.rs b/src/device/net/dev.rs index 388660ed..6c48ecae 100644 --- a/src/device/net/dev.rs +++ b/src/device/net/dev.rs @@ -9,9 +9,9 @@ use crate::{hal::Hal, transport::Transport, Error, Result}; /// Unlike [`VirtIONetRaw`], it uses [`NetBuffer`]s for transmission and /// reception rather than the raw slices. On initialization, it pre-allocates /// all receive buffers and puts them all in the receive queue. -/// +/// /// The virtio network device is a virtual ethernet card. -/// +/// /// It has enhanced rapidly and demonstrates clearly how support for new /// features are added to an existing device. /// Empty buffers are placed in one virtqueue for receiving packets, and diff --git a/src/device/net/dev_raw.rs b/src/device/net/dev_raw.rs index 9852ad40..fbcb63a3 100644 --- a/src/device/net/dev_raw.rs +++ b/src/device/net/dev_raw.rs @@ -53,7 +53,7 @@ impl VirtIONetRaw Date: Wed, 27 Sep 2023 21:40:15 +0800 Subject: [PATCH 06/14] fix: workflow test bugs --- examples/aarch64/src/main.rs | 1 + examples/riscv/src/tcp.rs | 2 +- examples/x86_64/Makefile | 6 ------ examples/x86_64/src/tcp.rs | 10 ++++----- src/device/net/dev.rs | 10 +++++++-- src/device/net/dev_raw.rs | 39 ++++++++++++++++++------------------ 6 files changed, 34 insertions(+), 34 deletions(-) diff --git a/examples/aarch64/src/main.rs b/examples/aarch64/src/main.rs index c3ec0b3c..7c0ca64a 100644 --- a/examples/aarch64/src/main.rs +++ b/examples/aarch64/src/main.rs @@ -30,6 +30,7 @@ use virtio_drivers::{ blk::VirtIOBlk, console::VirtIOConsole, gpu::VirtIOGpu, + net::VirtIONetRaw, socket::{ VirtIOSocket, VsockAddr, VsockConnectionManager, VsockEventType, VMADDR_CID_HOST, }, diff --git a/examples/riscv/src/tcp.rs b/examples/riscv/src/tcp.rs index 66c89a71..69c6e2b8 100644 --- a/examples/riscv/src/tcp.rs +++ b/examples/riscv/src/tcp.rs @@ -90,7 +90,7 @@ impl TxToken for VirtioTxToken { F: FnOnce(&mut [u8]) -> R, { let mut dev = self.0.borrow_mut(); - let mut tx_buf = dev.new_tx_buffer(len).unwrap(); + let mut tx_buf = dev.new_tx_buffer(len); let result = f(tx_buf.packet_mut()); trace!("SEND {} bytes: {:02X?}", len, tx_buf.packet()); dev.transmit_wait(tx_buf).unwrap(); diff --git a/examples/x86_64/Makefile b/examples/x86_64/Makefile index e22d01d4..bad89bae 100644 --- a/examples/x86_64/Makefile +++ b/examples/x86_64/Makefile @@ -20,12 +20,6 @@ else BUILD_ARGS += --no-default-features endif -ifeq ($(tcp), on) - BUILD_ARGS += --features tcp -else - BUILD_ARGS += --no-default-features -endif - QEMU_ARGS += \ -machine q35 \ -serial mon:stdio \ diff --git a/examples/x86_64/src/tcp.rs b/examples/x86_64/src/tcp.rs index 30ff6db4..07c2c3f6 100644 --- a/examples/x86_64/src/tcp.rs +++ b/examples/x86_64/src/tcp.rs @@ -1,6 +1,6 @@ //! Simple echo server over TCP. //! -//! Ref: https://github.com/smoltcp-rs/smoltcp/blob/master/examples/server.rs +//! Ref: use alloc::{borrow::ToOwned, rc::Rc, vec, vec::Vec}; use core::{cell::RefCell, str::FromStr}; @@ -9,7 +9,7 @@ use smoltcp::iface::{Config, Interface, SocketSet}; use smoltcp::phy::{Device, DeviceCapabilities, Medium, RxToken, TxToken}; use smoltcp::wire::{EthernetAddress, IpAddress, IpCidr, Ipv4Address}; use smoltcp::{socket::tcp, time::Instant}; -use virtio_drivers::device::net::{NetBuffer, VirtIONet}; +use virtio_drivers::device::net::{RxBuffer, VirtIONet}; use virtio_drivers::{transport::Transport, Error}; use super::{HalImpl, NET_QUEUE_SIZE}; @@ -64,7 +64,7 @@ impl Device for DeviceWrapper { } } -struct VirtioRxToken(Rc>>, NetBuffer); +struct VirtioRxToken(Rc>>, RxBuffer); struct VirtioTxToken(Rc>>); impl RxToken for VirtioRxToken { @@ -90,10 +90,10 @@ impl TxToken for VirtioTxToken { F: FnOnce(&mut [u8]) -> R, { let mut dev = self.0.borrow_mut(); - let mut tx_buf = dev.new_tx_buffer(len).unwrap(); + let mut tx_buf = dev.new_tx_buffer(len); let result = f(tx_buf.packet_mut()); trace!("SEND {} bytes: {:02X?}", len, tx_buf.packet()); - dev.transmit_wait(tx_buf).unwrap(); + dev.transmit(tx_buf).unwrap(); result } } diff --git a/src/device/net/dev.rs b/src/device/net/dev.rs index 6c48ecae..aa96573d 100644 --- a/src/device/net/dev.rs +++ b/src/device/net/dev.rs @@ -6,7 +6,7 @@ use crate::{hal::Hal, transport::Transport, Error, Result}; /// Driver for a VirtIO block device. /// -/// Unlike [`VirtIONetRaw`], it uses [`NetBuffer`]s for transmission and +/// Unlike [`VirtIONetRaw`], it uses [`RxBuffer`]s for transmission and /// reception rather than the raw slices. On initialization, it pre-allocates /// all receive buffers and puts them all in the receive queue. /// @@ -117,9 +117,15 @@ impl VirtIONet TxBuffer(vec![0; buf_len]) } + /// Sends a [`TxBuffer`] to the network, and blocks until the request + /// completed. Returns number of bytes transmitted. + pub fn transmit_wait(&mut self, tx_buf: TxBuffer) -> Result { + self.inner.transmit_wait(tx_buf.packet()) + } + /// Sends a [`TxBuffer`] to the network, and blocks until the request /// completed. pub fn send(&mut self, tx_buf: TxBuffer) -> Result { - self.inner.send(tx_buf) + self.inner.send(tx_buf.packet()) } } diff --git a/src/device/net/dev_raw.rs b/src/device/net/dev_raw.rs index fbcb63a3..37e720f7 100644 --- a/src/device/net/dev_raw.rs +++ b/src/device/net/dev_raw.rs @@ -1,4 +1,3 @@ -use super::TxBuffer; use super::{Config, EthernetAddress, Features, VirtioNetHdr}; use super::{MIN_BUFFER_LEN, NET_HDR_SIZE, QUEUE_RECEIVE, QUEUE_TRANSMIT}; use crate::hal::Hal; @@ -242,11 +241,26 @@ impl VirtIONetRaw Result { + let token = unsafe { self.transmit_begin(tx_buf)? }; + while self.poll_transmit().is_none() { + core::hint::spin_loop(); + } + unsafe { self.transmit_complete(token, tx_buf) } + } + + /// Sends a packet to the network, and blocks until the request /// completed. - pub fn send(&mut self, tx_buf: TxBuffer) -> Result { + pub fn send(&mut self, tx_buf: &[u8]) -> Result { let header = VirtioNetHdr::default(); - if tx_buf.packet_len() == 0 { + if tx_buf.len() == 0 { // Special case sending an empty packet, to avoid adding an empty buffer to the // virtqueue. self.send_queue.add_notify_wait_pop( @@ -256,7 +270,7 @@ impl VirtIONetRaw VirtIONetRaw Result { - let token = unsafe { self.transmit_begin(tx_buf)? }; - while self.poll_transmit().is_none() { - core::hint::spin_loop(); - } - unsafe { self.transmit_complete(token, tx_buf) } - } - /// Blocks and waits for a packet to be received. /// /// After completion, the `rx_buf` will contain a header followed by the From 8f288ead79e13e83648a558b67836549be86c1e1 Mon Sep 17 00:00:00 2001 From: hky1999 <976929993@qq.com> Date: Sun, 12 Nov 2023 15:44:30 +0800 Subject: [PATCH 07/14] fix: combine enable_dev_notify and disable_dev_notify --- src/device/net/dev.rs | 2 +- src/device/net/dev_raw.rs | 16 ++++++++-------- src/queue.rs | 24 ++++++------------------ 3 files changed, 15 insertions(+), 27 deletions(-) diff --git a/src/device/net/dev.rs b/src/device/net/dev.rs index aa96573d..d3c3119d 100644 --- a/src/device/net/dev.rs +++ b/src/device/net/dev.rs @@ -4,7 +4,7 @@ use super::net_buf::{RxBuffer, TxBuffer}; use super::{EthernetAddress, VirtIONetRaw}; use crate::{hal::Hal, transport::Transport, Error, Result}; -/// Driver for a VirtIO block device. +/// Driver for a VirtIO network device. /// /// Unlike [`VirtIONetRaw`], it uses [`RxBuffer`]s for transmission and /// reception rather than the raw slices. On initialization, it pre-allocates diff --git a/src/device/net/dev_raw.rs b/src/device/net/dev_raw.rs index 37e720f7..39a84529 100644 --- a/src/device/net/dev_raw.rs +++ b/src/device/net/dev_raw.rs @@ -70,14 +70,14 @@ impl VirtIONetRaw VirtIONetRaw VirtIONetRaw VirtIONetRaw VirtIONetRaw Result { let header = VirtioNetHdr::default(); - if tx_buf.len() == 0 { + if tx_buf.is_empty() { // Special case sending an empty packet, to avoid adding an empty buffer to the // virtqueue. self.send_queue.add_notify_wait_pop( diff --git a/src/queue.rs b/src/queue.rs index e317e4be..9d1d88c2 100644 --- a/src/queue.rs +++ b/src/queue.rs @@ -316,27 +316,15 @@ impl VirtQueue { unsafe { self.pop_used(token, inputs, outputs) } } - /// Advise the device that notifications are needed. + /// Advise the device whether used buffer notifications are needed. /// /// See Virtio v1.1 2.6.7 Used Buffer Notification Suppression - pub fn enable_dev_notify(&mut self) { + pub fn set_dev_notify(&mut self, enable: bool) { + let avail_ring_flags = if enable { 0x0000 } else { 0x0001 }; if !self.event_idx { // Safe because self.avail points to a valid, aligned, initialised, dereferenceable, readable // instance of AvailRing. - unsafe { (*self.avail.as_ptr()).flags = 0x0000 } - } - // Write barrier so that device can see change to available index after this method returns. - fence(Ordering::SeqCst); - } - - /// Advise the device that notifications are not needed. - /// - /// See Virtio v1.1 2.6.7 Used Buffer Notification Suppression - pub fn disable_dev_notify(&mut self) { - if !self.event_idx { - // Safe because self.avail points to a valid, aligned, initialised, dereferenceable, readable - // instance of AvailRing. - unsafe { (*self.avail.as_ptr()).flags = 0x0001 } + unsafe { (*self.avail.as_ptr()).flags = avail_ring_flags } } // Write barrier so that device can see change to available index after this method returns. fence(Ordering::SeqCst); @@ -1163,12 +1151,12 @@ mod tests { // Check that the avail ring's flag is zero by default. assert_eq!(unsafe { (*queue.avail.as_ptr()).flags }, 0x0); - queue.disable_dev_notify(); + queue.set_dev_notify(false); // Check that the avail ring's flag is 1 after `disable_dev_notify`. assert_eq!(unsafe { (*queue.avail.as_ptr()).flags }, 0x1); - queue.enable_dev_notify(); + queue.set_dev_notify(true); // Check that the avail ring's flag is 0 after `enable_dev_notify`. assert_eq!(unsafe { (*queue.avail.as_ptr()).flags }, 0x0); From 20fe99c70e6483a82110d789f0449708b7fe3b3b Mon Sep 17 00:00:00 2001 From: hky1999 <976929993@qq.com> Date: Sun, 12 Nov 2023 16:45:06 +0800 Subject: [PATCH 08/14] fix: delete transmit_wait --- examples/aarch64/src/main.rs | 2 +- examples/riscv/src/main.rs | 2 +- examples/riscv/src/tcp.rs | 2 +- examples/x86_64/src/main.rs | 2 +- src/device/net/dev.rs | 6 ------ src/device/net/dev_raw.rs | 15 --------------- 6 files changed, 4 insertions(+), 25 deletions(-) diff --git a/examples/aarch64/src/main.rs b/examples/aarch64/src/main.rs index 7c0ca64a..9b9a98c0 100644 --- a/examples/aarch64/src/main.rs +++ b/examples/aarch64/src/main.rs @@ -203,7 +203,7 @@ fn virtio_net(transport: T) { pkt_len, &buf[hdr_len..hdr_len + pkt_len] ); - net.transmit_wait(&buf[..hdr_len + pkt_len]) + net.send(&buf[..hdr_len + pkt_len]) .expect("failed to send"); info!("virtio-net test finished"); } diff --git a/examples/riscv/src/main.rs b/examples/riscv/src/main.rs index 8e66ea2f..2e85dcde 100644 --- a/examples/riscv/src/main.rs +++ b/examples/riscv/src/main.rs @@ -159,7 +159,7 @@ fn virtio_net(transport: T) { pkt_len, &buf[hdr_len..hdr_len + pkt_len] ); - net.transmit_wait(&buf[..hdr_len + pkt_len]) + net.send(&buf[..hdr_len + pkt_len]) .expect("failed to send"); info!("virtio-net test finished"); } diff --git a/examples/riscv/src/tcp.rs b/examples/riscv/src/tcp.rs index 69c6e2b8..5f6623c3 100644 --- a/examples/riscv/src/tcp.rs +++ b/examples/riscv/src/tcp.rs @@ -93,7 +93,7 @@ impl TxToken for VirtioTxToken { let mut tx_buf = dev.new_tx_buffer(len); let result = f(tx_buf.packet_mut()); trace!("SEND {} bytes: {:02X?}", len, tx_buf.packet()); - dev.transmit_wait(tx_buf).unwrap(); + dev.send(tx_buf).unwrap(); result } } diff --git a/examples/x86_64/src/main.rs b/examples/x86_64/src/main.rs index 5c79b20f..b307c133 100644 --- a/examples/x86_64/src/main.rs +++ b/examples/x86_64/src/main.rs @@ -130,7 +130,7 @@ fn virtio_net(transport: T) { pkt_len, &buf[hdr_len..hdr_len + pkt_len] ); - net.transmit_wait(&buf[..hdr_len + pkt_len]) + net.send(&buf[..hdr_len + pkt_len]) .expect("failed to send"); info!("virtio-net test finished"); } diff --git a/src/device/net/dev.rs b/src/device/net/dev.rs index d3c3119d..5dfba600 100644 --- a/src/device/net/dev.rs +++ b/src/device/net/dev.rs @@ -117,12 +117,6 @@ impl VirtIONet TxBuffer(vec![0; buf_len]) } - /// Sends a [`TxBuffer`] to the network, and blocks until the request - /// completed. Returns number of bytes transmitted. - pub fn transmit_wait(&mut self, tx_buf: TxBuffer) -> Result { - self.inner.transmit_wait(tx_buf.packet()) - } - /// Sends a [`TxBuffer`] to the network, and blocks until the request /// completed. pub fn send(&mut self, tx_buf: TxBuffer) -> Result { diff --git a/src/device/net/dev_raw.rs b/src/device/net/dev_raw.rs index 39a84529..20b47d17 100644 --- a/src/device/net/dev_raw.rs +++ b/src/device/net/dev_raw.rs @@ -241,21 +241,6 @@ impl VirtIONetRaw Result { - let token = unsafe { self.transmit_begin(tx_buf)? }; - while self.poll_transmit().is_none() { - core::hint::spin_loop(); - } - unsafe { self.transmit_complete(token, tx_buf) } - } - /// Sends a packet to the network, and blocks until the request /// completed. pub fn send(&mut self, tx_buf: &[u8]) -> Result { From b2adb57f7dc2972104f851c3b438a725450db621 Mon Sep 17 00:00:00 2001 From: hky1999 <976929993@qq.com> Date: Sun, 12 Nov 2023 19:19:50 +0800 Subject: [PATCH 09/14] feat: rebase to master --- src/device/net/mod.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/device/net/mod.rs b/src/device/net/mod.rs index 05a8f538..e9b081c8 100644 --- a/src/device/net/mod.rs +++ b/src/device/net/mod.rs @@ -12,7 +12,7 @@ pub use self::{dev::VirtIONet, net_buf::RxBuffer, net_buf::TxBuffer}; use crate::volatile::ReadOnly; use bitflags::bitflags; -use zerocopy::{AsBytes, FromBytes}; +use zerocopy::{AsBytes, FromBytes, FromZeroes}; const MAX_BUFFER_LEN: usize = 65535; const MIN_BUFFER_LEN: usize = 1526; @@ -112,7 +112,7 @@ type EthernetAddress = [u8; 6]; /// and buffers for incoming packets are placed in the receiveq1. . .receiveqN. /// In each case, the packet itself is preceded by a header. #[repr(C)] -#[derive(AsBytes, Debug, Default, FromBytes)] +#[derive(AsBytes, Debug, Default, FromBytes, FromZeroes)] pub struct VirtioNetHdr { flags: Flags, gso_type: GsoType, @@ -124,7 +124,7 @@ pub struct VirtioNetHdr { // payload starts from here } -#[derive(AsBytes, Copy, Clone, Debug, Default, Eq, FromBytes, PartialEq)] +#[derive(AsBytes, Copy, Clone, Debug, Default, Eq, FromBytes, FromZeroes, PartialEq)] #[repr(transparent)] struct Flags(u8); @@ -137,7 +137,7 @@ bitflags! { } #[repr(transparent)] -#[derive(AsBytes, Debug, Copy, Clone, Default, Eq, FromBytes, PartialEq)] +#[derive(AsBytes, Debug, Copy, Clone, Default, Eq, FromBytes, FromZeroes, PartialEq)] struct GsoType(u8); impl GsoType { From a3da9bba2e6dd09fc0985cf6193ac46f12105763 Mon Sep 17 00:00:00 2001 From: hky1999 <976929993@qq.com> Date: Sun, 12 Nov 2023 19:58:08 +0800 Subject: [PATCH 10/14] fix: cargo fmt in examples --- examples/aarch64/src/main.rs | 3 +-- examples/riscv/src/main.rs | 3 +-- examples/x86_64/src/main.rs | 3 +-- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/examples/aarch64/src/main.rs b/examples/aarch64/src/main.rs index 9b9a98c0..849e37b7 100644 --- a/examples/aarch64/src/main.rs +++ b/examples/aarch64/src/main.rs @@ -203,8 +203,7 @@ fn virtio_net(transport: T) { pkt_len, &buf[hdr_len..hdr_len + pkt_len] ); - net.send(&buf[..hdr_len + pkt_len]) - .expect("failed to send"); + net.send(&buf[..hdr_len + pkt_len]).expect("failed to send"); info!("virtio-net test finished"); } diff --git a/examples/riscv/src/main.rs b/examples/riscv/src/main.rs index 2e85dcde..dd0c5f06 100644 --- a/examples/riscv/src/main.rs +++ b/examples/riscv/src/main.rs @@ -159,8 +159,7 @@ fn virtio_net(transport: T) { pkt_len, &buf[hdr_len..hdr_len + pkt_len] ); - net.send(&buf[..hdr_len + pkt_len]) - .expect("failed to send"); + net.send(&buf[..hdr_len + pkt_len]).expect("failed to send"); info!("virtio-net test finished"); } diff --git a/examples/x86_64/src/main.rs b/examples/x86_64/src/main.rs index b307c133..f9c9f3ed 100644 --- a/examples/x86_64/src/main.rs +++ b/examples/x86_64/src/main.rs @@ -130,8 +130,7 @@ fn virtio_net(transport: T) { pkt_len, &buf[hdr_len..hdr_len + pkt_len] ); - net.send(&buf[..hdr_len + pkt_len]) - .expect("failed to send"); + net.send(&buf[..hdr_len + pkt_len]).expect("failed to send"); info!("virtio-net test finished"); } From 8b38f2494e381b5a8712f190e60c3bd12d114ce6 Mon Sep 17 00:00:00 2001 From: hky1999 <976929993@qq.com> Date: Sun, 12 Nov 2023 20:12:23 +0800 Subject: [PATCH 11/14] feat: add RING_EVENT_IDX feature in net dev --- src/device/net/dev_raw.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/device/net/dev_raw.rs b/src/device/net/dev_raw.rs index 20b47d17..5f99d525 100644 --- a/src/device/net/dev_raw.rs +++ b/src/device/net/dev_raw.rs @@ -1,5 +1,5 @@ use super::{Config, EthernetAddress, Features, VirtioNetHdr}; -use super::{MIN_BUFFER_LEN, NET_HDR_SIZE, QUEUE_RECEIVE, QUEUE_TRANSMIT}; +use super::{MIN_BUFFER_LEN, NET_HDR_SIZE, QUEUE_RECEIVE, QUEUE_TRANSMIT, SUPPORTED_FEATURES}; use crate::hal::Hal; use crate::queue::VirtQueue; use crate::transport::Transport; @@ -26,7 +26,7 @@ pub struct VirtIONetRaw { impl VirtIONetRaw { /// Create a new VirtIO-Net driver. pub fn new(mut transport: T) -> Result { - let negotiated_features = transport.begin_init(Features::MAC | Features::STATUS); + let negotiated_features = transport.begin_init(SUPPORTED_FEATURES); info!("negotiated_features {:?}", negotiated_features); // read configuration space let config = transport.config_space::()?; From b1fdc1105b884cf92e8db8cf7fbb34e276b9634f Mon Sep 17 00:00:00 2001 From: hky1999 <976929993@qq.com> Date: Tue, 14 Nov 2023 20:42:35 +0800 Subject: [PATCH 12/14] feat: modify can send, delete can_recv --- src/device/net/dev_raw.rs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/device/net/dev_raw.rs b/src/device/net/dev_raw.rs index 5f99d525..eb6193f7 100644 --- a/src/device/net/dev_raw.rs +++ b/src/device/net/dev_raw.rs @@ -87,12 +87,7 @@ impl VirtIONetRaw bool { - self.send_queue.available_desc() >= 1 - } - - /// Whether can receive packet. - pub fn can_recv(&self) -> bool { - self.recv_queue.can_pop() + self.send_queue.available_desc() >= 2 } /// Whether the length of the receive buffer is valid. From ceb6ee190ecc8135f6592064a3e0b55811ed193f Mon Sep 17 00:00:00 2001 From: Andrew Walbran Date: Tue, 14 Nov 2023 12:51:45 +0000 Subject: [PATCH 13/14] Implement can_recv in terms of poll_receive. --- src/device/net/dev.rs | 2 +- src/device/net/dev_raw.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/device/net/dev.rs b/src/device/net/dev.rs index 5dfba600..33448637 100644 --- a/src/device/net/dev.rs +++ b/src/device/net/dev.rs @@ -67,7 +67,7 @@ impl VirtIONet /// Whether can receive packet. pub fn can_recv(&self) -> bool { - self.inner.can_recv() + self.inner.poll_receive().is_some() } /// Receives a [`RxBuffer`] from network. If currently no data, returns an diff --git a/src/device/net/dev_raw.rs b/src/device/net/dev_raw.rs index eb6193f7..dcdd61cf 100644 --- a/src/device/net/dev_raw.rs +++ b/src/device/net/dev_raw.rs @@ -210,7 +210,7 @@ impl VirtIONetRaw Option { + pub fn poll_receive(&self) -> Option { self.recv_queue.peek_used() } From 84573331bc56c8994aae080d20ef23f3bab94236 Mon Sep 17 00:00:00 2001 From: Andrew Walbran Date: Tue, 14 Nov 2023 12:56:44 +0000 Subject: [PATCH 14/14] Fix typos. --- src/device/net/dev_raw.rs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/device/net/dev_raw.rs b/src/device/net/dev_raw.rs index dcdd61cf..2c036f31 100644 --- a/src/device/net/dev_raw.rs +++ b/src/device/net/dev_raw.rs @@ -134,7 +134,7 @@ impl VirtIONetRaw VirtIONetRaw VirtIONetRaw VirtIONetRaw Result { let header = VirtioNetHdr::default(); if tx_buf.is_empty() {