Browse Source

Introduce VirtIONetRaw to allow custom NIC buffer management and used buffer notification suppression (#111)

* Introduce `VirtIONetRaw` to allow custom NIC buffer management

* wip: add used buffer notification suppression but do not work

* feat: disable_interrupts by disable_dev_notify of both xmit_queue and recv_queue

* feat: rebase new-netdev to master and support netdev used buffer notification suppression

* fix: cargo fmt

* fix: workflow test bugs

* fix: combine enable_dev_notify and disable_dev_notify

* fix: delete transmit_wait

* feat: rebase to master

* fix: cargo fmt in examples

* feat: add RING_EVENT_IDX feature in net dev

* feat: modify can send, delete can_recv

* Implement can_recv in terms of poll_receive.

* Fix typos.

---------

Co-authored-by: Yuekai Jia <[email protected]>
Co-authored-by: Andrew Walbran <[email protected]>
hky1999 1 year ago
parent
commit
3da64ea3a9

+ 16 - 1
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,
         },
@@ -137,7 +138,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 +193,20 @@ fn virtio_gpu<T: Transport>(transport: T) {
     info!("virtio-gpu test finished");
 }
 
+fn virtio_net<T: Transport>(transport: T) {
+    let mut net =
+        VirtIONetRaw::<HalImpl, T, 16>::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.send(&buf[..hdr_len + pkt_len]).expect("failed to send");
+    info!("virtio-net test finished");
+}
+
 fn virtio_console<T: Transport>(transport: T) {
     let mut console =
         VirtIOConsole::<HalImpl, T>::new(transport).expect("Failed to create console driver");

+ 24 - 21
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,33 @@ fn virtio_input<T: Transport>(transport: T) {
 }
 
 fn virtio_net<T: Transport>(transport: T) {
-    let net = VirtIONet::<HalImpl, T, NET_QUEUE_SIZE>::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::<HalImpl, T, NET_QUEUE_SIZE>::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.send(&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::<HalImpl, T, NET_QUEUE_SIZE>::new(
+            transport,
+            NET_BUFFER_LEN,
+        )
+        .expect("failed to create net driver");
+        info!("MAC address: {:02x?}", net.mac_address());
+        tcp::test_echo_server(net);
+    }
 }

+ 2 - 2
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<T: Transport> Device for DeviceWrapper<T> {
     }
 }
 
-struct VirtioRxToken<T: Transport>(Rc<RefCell<DeviceImpl<T>>>, RxBuffer);
+struct VirtioRxToken<T: Transport>(Rc<RefCell<DeviceImpl<T>>>, NetBuffer);
 struct VirtioTxToken<T: Transport>(Rc<RefCell<DeviceImpl<T>>>);
 
 impl<T: Transport> RxToken for VirtioRxToken<T> {

+ 1 - 6
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,11 +20,6 @@ else
 	BUILD_ARGS += --no-default-features
 endif
 
-VSOCK_BUILD_ARGS =
-ifeq ($(mode), release)
-	VSOCK_BUILD_ARGS += --release
-endif
-
 QEMU_ARGS += \
 	-machine q35 \
 	-serial mon:stdio \

+ 24 - 21
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,35 @@ fn virtio_gpu<T: Transport>(transport: T) {
 }
 
 fn virtio_net<T: Transport>(transport: T) {
-    let net = VirtIONet::<HalImpl, T, NET_QUEUE_SIZE>::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::<HalImpl, T, NET_QUEUE_SIZE>::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.send(&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::<HalImpl, T, NET_QUEUE_SIZE>::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) {

+ 2 - 2
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: <https://github.com/smoltcp-rs/smoltcp/blob/master/examples/server.rs>
 
 use alloc::{borrow::ToOwned, rc::Rc, vec, vec::Vec};
 use core::{cell::RefCell, str::FromStr};
@@ -93,7 +93,7 @@ impl<T: Transport> TxToken for VirtioTxToken<T> {
         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.send(tx_buf).unwrap();
+        dev.transmit(tx_buf).unwrap();
         result
     }
 }

+ 2 - 1
src/device/mod.rs

@@ -7,8 +7,9 @@ pub mod console;
 pub mod gpu;
 #[cfg(feature = "alloc")]
 pub mod input;
-#[cfg(feature = "alloc")]
+
 pub mod net;
+
 pub mod socket;
 
 pub(crate) mod common;

+ 0 - 413
src/device/net.rs

@@ -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::<VirtioNetHdr>();
-
-/// A buffer used for transmitting.
-pub struct TxBuffer(Vec<u8>);
-
-/// A buffer used for receiving.
-pub struct RxBuffer {
-    buf: Vec<usize>, // 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::<usize>()],
-            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<H: Hal, T: Transport, const QUEUE_SIZE: usize> {
-    transport: T,
-    mac: EthernetAddress,
-    recv_queue: VirtQueue<H, QUEUE_SIZE>,
-    send_queue: VirtQueue<H, QUEUE_SIZE>,
-    rx_buffers: [Option<RxBuffer>; QUEUE_SIZE],
-}
-
-impl<H: Hal, T: Transport, const QUEUE_SIZE: usize> VirtIONet<H, T, QUEUE_SIZE> {
-    /// Create a new VirtIO-Net driver.
-    pub fn new(mut transport: T, buf_len: usize) -> Result<Self> {
-        let negotiated_features = transport.begin_init(SUPPORTED_FEATURES);
-        // read configuration space
-        let config = transport.config_space::<Config>()?;
-        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<RxBuffer> = 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<RxBuffer> {
-        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<H: Hal, T: Transport, const QUEUE_SIZE: usize> Drop for VirtIONet<H, T, QUEUE_SIZE> {
-    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<EthernetAddress>,
-    status: ReadOnly<Status>,
-    max_virtqueue_pairs: ReadOnly<u16>,
-    mtu: ReadOnly<u16>,
-}
-
-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);

+ 125 - 0
src/device/net/dev.rs

@@ -0,0 +1,125 @@
+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 network device.
+///
+/// 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.
+///
+/// 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<H: Hal, T: Transport, const QUEUE_SIZE: usize> {
+    inner: VirtIONetRaw<H, T, QUEUE_SIZE>,
+    rx_buffers: [Option<RxBuffer>; QUEUE_SIZE],
+}
+
+impl<H: Hal, T: Transport, const QUEUE_SIZE: usize> VirtIONet<H, T, QUEUE_SIZE> {
+    /// Create a new VirtIO-Net driver.
+    pub fn new(transport: T, buf_len: usize) -> Result<Self> {
+        let mut inner = VirtIONetRaw::new(transport)?;
+
+        const NONE_BUF: Option<RxBuffer> = 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 { 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()
+    }
+
+    /// 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 send packet.
+    pub fn can_send(&self) -> bool {
+        self.inner.can_send()
+    }
+
+    /// Whether can receive packet.
+    pub fn can_recv(&self) -> bool {
+        self.inner.poll_receive().is_some()
+    }
+
+    /// 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<RxBuffer> {
+        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.
+            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: 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()) }?;
+        // `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);
+        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 {
+        self.inner.send(tx_buf.packet())
+    }
+}

+ 281 - 0
src/device/net/dev_raw.rs

@@ -0,0 +1,281 @@
+use super::{Config, EthernetAddress, Features, VirtioNetHdr};
+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;
+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<H: Hal, T: Transport, const QUEUE_SIZE: usize> {
+    transport: T,
+    mac: EthernetAddress,
+    recv_queue: VirtQueue<H, QUEUE_SIZE>,
+    send_queue: VirtQueue<H, QUEUE_SIZE>,
+}
+
+impl<H: Hal, T: Transport, const QUEUE_SIZE: usize> VirtIONetRaw<H, T, QUEUE_SIZE> {
+    /// Create a new VirtIO-Net driver.
+    pub fn new(mut transport: T) -> Result<Self> {
+        let negotiated_features = transport.begin_init(SUPPORTED_FEATURES);
+        info!("negotiated_features {:?}", negotiated_features);
+        // read configuration space
+        let config = transport.config_space::<Config>()?;
+        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 send_queue = VirtQueue::new(
+            &mut transport,
+            QUEUE_TRANSMIT,
+            false,
+            negotiated_features.contains(Features::RING_EVENT_IDX),
+        )?;
+        let recv_queue = VirtQueue::new(
+            &mut transport,
+            QUEUE_RECEIVE,
+            false,
+            negotiated_features.contains(Features::RING_EVENT_IDX),
+        )?;
+
+        transport.finish_init();
+
+        Ok(VirtIONetRaw {
+            transport,
+            mac,
+            recv_queue,
+            send_queue,
+        })
+    }
+
+    /// Acknowledge interrupt.
+    pub fn ack_interrupt(&mut self) -> bool {
+        self.transport.ack_interrupt()
+    }
+
+    /// Disable interrupts.
+    pub fn disable_interrupts(&mut self) {
+        self.send_queue.set_dev_notify(false);
+        self.recv_queue.set_dev_notify(false);
+    }
+
+    /// Enable interrupts.
+    pub fn enable_interrupts(&mut self) {
+        self.send_queue.set_dev_notify(true);
+        self.recv_queue.set_dev_notify(true);
+    }
+
+    /// 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 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<usize> {
+        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 buffer 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<u16> {
+        Self::check_tx_buf_len(tx_buf)?;
+        let token = self.send_queue.add(&[tx_buf], &mut [])?;
+        if self.send_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<u16> {
+        self.send_queue.peek_used()
+    }
+
+    /// Completes a transmission operation which was started by [`transmit_begin`].
+    /// Returns number of bytes transmitted.
+    ///
+    /// # Safety
+    ///
+    /// The same buffer must be passed in again as was 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<usize> {
+        let len = self.send_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 buffer 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<u16> {
+        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(&self) -> Option<u16> {
+        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 buffer must be passed in again as was 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))
+    }
+
+    /// Sends a packet to the network, and blocks until the request completed.
+    pub fn send(&mut self, tx_buf: &[u8]) -> Result {
+        let header = VirtioNetHdr::default();
+        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(
+                &[header.as_bytes()],
+                &mut [],
+                &mut self.transport,
+            )?;
+        } else {
+            self.send_queue.add_notify_wait_pop(
+                &[header.as_bytes(), tx_buf],
+                &mut [],
+                &mut self.transport,
+            )?;
+        }
+        Ok(())
+    }
+
+    /// 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<H: Hal, T: Transport, const QUEUE_SIZE: usize> Drop for VirtIONetRaw<H, T, QUEUE_SIZE> {
+    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);
+    }
+}

+ 155 - 0
src/device/net/mod.rs

@@ -0,0 +1,155 @@
+//! 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::RxBuffer, net_buf::TxBuffer};
+
+use crate::volatile::ReadOnly;
+use bitflags::bitflags;
+use zerocopy::{AsBytes, FromBytes, FromZeroes};
+
+const MAX_BUFFER_LEN: usize = 65535;
+const MIN_BUFFER_LEN: usize = 1526;
+const NET_HDR_SIZE: usize = core::mem::size_of::<VirtioNetHdr>();
+
+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<EthernetAddress>,
+    status: ReadOnly<Status>,
+    max_virtqueue_pairs: ReadOnly<u16>,
+    mtu: ReadOnly<u16>,
+}
+
+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);

+ 83 - 0
src/device/net/net_buf.rs

@@ -0,0 +1,83 @@
+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 transmitting.
+pub struct TxBuffer(pub(crate) Vec<u8>);
+
+/// A buffer used for receiving.
+pub struct RxBuffer {
+    pub(crate) buf: Vec<usize>, // 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 RxBuffer {
+    /// Allocates a new buffer with length `buf_len`.
+    pub(crate) fn new(idx: usize, buf_len: usize) -> Self {
+        Self {
+            buf: vec![0; buf_len / size_of::<usize>()],
+            packet_len: 0,
+            idx: idx.try_into().unwrap(),
+        }
+    }
+
+    /// 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]
+    }
+}

+ 45 - 0
src/queue.rs

@@ -316,6 +316,20 @@ impl<H: Hal, const SIZE: usize> VirtQueue<H, SIZE> {
         unsafe { self.pop_used(token, inputs, outputs) }
     }
 
+    /// Advise the device whether used buffer notifications are needed.
+    ///
+    /// See Virtio v1.1 2.6.7 Used Buffer Notification Suppression
+    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 = avail_ring_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
     /// virtqueue.
     ///
@@ -1117,6 +1131,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 mut queue = VirtQueue::<FakeHal, 4>::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.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.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);
+    }
+
     /// Tests that the queue notifies the device about added buffers, if it hasn't suppressed
     /// notifications.
     #[test]