diff --git a/examples/aarch64/src/main.rs b/examples/aarch64/src/main.rs index bc2e2156..849e37b7 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, }, @@ -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(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.send(&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..dd0c5f06 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,33 @@ 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.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::::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..5f6623c3 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 { diff --git a/examples/x86_64/Makefile b/examples/x86_64/Makefile index 35449231..bad89bae 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,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 \ diff --git a/examples/x86_64/src/main.rs b/examples/x86_64/src/main.rs index e9397d73..f9c9f3ed 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,35 @@ 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.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::::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..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}; @@ -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.send(tx_buf).unwrap(); + dev.transmit(tx_buf).unwrap(); result } } diff --git a/src/device/mod.rs b/src/device/mod.rs index 00fa6fe2..d8e6389d 100644 --- a/src/device/mod.rs +++ b/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; 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 new file mode 100644 index 00000000..33448637 --- /dev/null +++ b/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 { + 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 = 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 { + 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()) + } +} diff --git a/src/device/net/dev_raw.rs b/src/device/net/dev_raw.rs new file mode 100644 index 00000000..2c036f31 --- /dev/null +++ b/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 { + transport: T, + mac: EthernetAddress, + recv_queue: VirtQueue, + send_queue: VirtQueue, +} + +impl VirtIONetRaw { + /// Create a new VirtIO-Net driver. + pub fn new(mut transport: T) -> Result { + let negotiated_features = transport.begin_init(SUPPORTED_FEATURES); + info!("negotiated_features {:?}", negotiated_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) + ); + } + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 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..e9b081c8 --- /dev/null +++ b/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::(); + +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/net_buf.rs b/src/device/net/net_buf.rs new file mode 100644 index 00000000..8b4947bf --- /dev/null +++ b/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); + +/// 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 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::()], + 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] + } +} diff --git a/src/queue.rs b/src/queue.rs index cc103256..9d1d88c2 100644 --- a/src/queue.rs +++ b/src/queue.rs @@ -316,6 +316,20 @@ impl VirtQueue { 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::::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]