Add virtio net device driver

This commit is contained in:
Jianfeng Jiang
2023-05-30 16:34:28 +08:00
committed by Tate, Hongliang Tian
parent 2985cdced6
commit 7304e06c88
20 changed files with 875 additions and 15 deletions

View File

@ -9,10 +9,11 @@ use jinux_pci::{
};
use jinux_util::frame_ptr::InFramePtr;
use self::input::device::InputDevice;
use self::{input::device::InputDevice, network::device::NetworkDevice};
pub mod block;
pub mod input;
pub mod network;
pub(crate) const PCI_VIRTIO_CAP_COMMON_CFG: u8 = 1;
pub(crate) const PCI_VIRTIO_CAP_NOTIFY_CFG: u8 = 2;
@ -20,9 +21,8 @@ pub(crate) const PCI_VIRTIO_CAP_ISR_CFG: u8 = 3;
pub(crate) const PCI_VIRTIO_CAP_DEVICE_CFG: u8 = 4;
pub(crate) const PCI_VIRTIO_CAP_PCI_CFG: u8 = 5;
#[derive(Debug)]
pub enum VirtioDevice {
Network,
Network(NetworkDevice),
Block(BLKDevice),
Console,
Entropy,
@ -141,6 +141,14 @@ impl VirtioDevice {
virtio_info.notify_off_multiplier,
msix_vector_left,
)?),
VirtioDeviceType::Network => VirtioDevice::Network(NetworkDevice::new(
&virtio_info.device_cap_cfg,
bars,
&virtio_info.common_cfg_frame_ptr,
virtio_info.notify_base_address as usize,
virtio_info.notify_off_multiplier,
msix_vector_left,
)?),
_ => {
panic!("initialize PCIDevice failed, unsupport Virtio Device Type")
}
@ -152,7 +160,9 @@ impl VirtioDevice {
let mask = ((1u64 << 24) - 1) | (((1u64 << 24) - 1) << 50);
let device_specified_features = features & mask;
let device_support_features = match device_type {
VirtioDeviceType::Network => todo!(),
VirtioDeviceType::Network => {
NetworkDevice::negotiate_features(device_specified_features)
}
VirtioDeviceType::Block => BLKDevice::negotiate_features(device_specified_features),
VirtioDeviceType::Console => todo!(),
VirtioDeviceType::Entropy => todo!(),

View File

@ -0,0 +1,84 @@
use align_ext::AlignExt;
use bytes::BytesMut;
use pod::Pod;
use crate::device::network::header::VIRTIO_NET_HDR_LEN;
use super::header::VirtioNetHdr;
/// Buffer for receive packet
#[derive(Debug)]
pub struct RxBuffer {
/// Packet Buffer, length align 8.
buf: BytesMut,
/// Packet len
packet_len: usize,
}
impl RxBuffer {
pub fn new(len: usize) -> Self {
let len = len.align_up(8);
let buf = BytesMut::zeroed(len);
Self { buf, packet_len: 0 }
}
pub const fn packet_len(&self) -> usize {
self.packet_len
}
pub fn set_packet_len(&mut self, packet_len: usize) {
self.packet_len = packet_len;
}
pub fn buf(&self) -> &[u8] {
&self.buf
}
pub fn buf_mut(&mut self) -> &mut [u8] {
&mut self.buf
}
/// Packet payload slice, which is inner buffer excluding VirtioNetHdr.
pub fn packet(&self) -> &[u8] {
debug_assert!(VIRTIO_NET_HDR_LEN + self.packet_len <= self.buf.len());
&self.buf[VIRTIO_NET_HDR_LEN..VIRTIO_NET_HDR_LEN + self.packet_len]
}
/// Mutable packet payload slice.
pub fn packet_mut(&mut self) -> &mut [u8] {
debug_assert!(VIRTIO_NET_HDR_LEN + self.packet_len <= self.buf.len());
&mut self.buf[VIRTIO_NET_HDR_LEN..VIRTIO_NET_HDR_LEN + self.packet_len]
}
pub fn virtio_net_header(&self) -> VirtioNetHdr {
VirtioNetHdr::from_bytes(&self.buf[..VIRTIO_NET_HDR_LEN])
}
}
/// Buffer for transmit packet
#[derive(Debug)]
pub struct TxBuffer {
buf: BytesMut,
}
impl TxBuffer {
pub fn with_len(buf_len: usize) -> Self {
Self {
buf: BytesMut::zeroed(buf_len),
}
}
pub fn new(buf: &[u8]) -> Self {
Self {
buf: BytesMut::from(buf),
}
}
pub fn buf(&self) -> &[u8] {
&self.buf
}
pub fn buf_mut(&mut self) -> &mut [u8] {
&mut self.buf
}
}

View File

@ -0,0 +1,82 @@
use bitflags::bitflags;
use jinux_pci::{capability::vendor::virtio::CapabilityVirtioData, util::BAR};
use jinux_util::frame_ptr::InFramePtr;
use pod::Pod;
use super::device::EthernetAddr;
bitflags! {
/// Virtio Net Feature bits.
pub struct NetworkFeatures: u64 {
const VIRTIO_NET_F_CSUM = 1 << 0; // Device handles packets with partial checksum.
const VIRTIO_NET_F_GUEST_CSUM = 1 << 1; // Driver handles packets with partial checksum
const VIRTIO_NET_F_CTRL_GUEST_OFFLOADS = 1 << 2;// Control channel offloads reconfiguration support
const VIRTIO_NET_F_MTU = 1 << 3; // Device maximum MTU reporting is supported
const VIRTIO_NET_F_MAC = 1 << 5; // Device has given MAC address.
const VIRTIO_NET_F_GUEST_TSO4 = 1 << 7; // Driver can receive TSOv4.
const VIRTIO_NET_F_GUEST_TSO6 = 1 <<8; // Driver can receive TSOv6.
const VIRTIO_NET_F_GUEST_ECN = 1 << 9; // Driver can receive TSO with ECN.
const VIRTIO_NET_F_GUEST_UFO = 1 << 10; // Driver can receive UFO.
const VIRTIO_NET_F_HOST_TSO4 = 1 << 11; // Device can receive TSOv4.
const VIRTIO_NET_F_HOST_TSO6 = 1 <<12; // Device can receive TSOv6.
const VIRTIO_NET_F_HOST_ECN = 1 << 13; // Device can receive TSO with ECN.
const VIRTIO_NET_F_HOST_UFO = 1 << 14; // Device can receive UFO.
const VIRTIO_NET_F_MRG_RXBUF = 1 << 15; // Driver can merge receive buffers.
const VIRTIO_NET_F_STATUS = 1 << 16; // Configuration status field is available.
const VIRTIO_NET_F_CTRL_VQ = 1 << 17; // Control channel is available.
const VIRTIO_NET_F_CTRL_RX = 1 << 18; // Control channel RX mode support.
const VIRTIO_NET_F_CTRL_VLAN = 1 << 19; // Control channel VLAN filtering.
const VIRTIO_NET_F_EXTRA = 1 << 20; //
const VIRTIO_NET_F_GUEST_ANNOUNCE = 1 << 21; // Driver can send gratuitous packets.
const VIRTIO_NET_F_MQ = 1 << 22; // Device supports multiqueue with automatic receive steering.
const VIRTIO_NET_F_CTRL_MAC_ADDR = 1 << 23; // Set MAC address through control channel.
// const VIRTIO_NET_F_HOST_USO = 1 << 56; // Device can receive USO packets.
// const VIRTIO_NET_F_HASH_REPORT = 1 << 57; // Device can report per-packet hash value and a type of calculated hash.
// const VIRTIO_NET_F_GUEST_HDRLEN = 1 << 59; // Driver can provide the exact hdr_len value. Device benefits from knowing the exact header length.
// const VIRTIO_NET_F_RSS = 1 << 60; // Device supports RSS (receive-side scaling) with Toeplitz hash calculation and configurable hash parameters for receive steering.
// const VIRTIO_NET_F_RSC_EXT = 1 << 61; // DevicecanprocessduplicatedACKsandreportnumberofcoalescedseg- ments and duplicated ACKs.
// const VIRTIO_NET_F_STANDBY = 1 << 62; // Device may act as a standby for a primary device with the same MAC address.
// const VIRTIO_NET_F_SPEED_DUPLEX = 1 << 63; // Device reports speed and duplex.
}
}
impl NetworkFeatures {
pub fn support_features() -> Self {
NetworkFeatures::VIRTIO_NET_F_MAC | NetworkFeatures::VIRTIO_NET_F_STATUS
}
}
bitflags! {
#[repr(C)]
#[derive(Pod)]
pub struct Status: u16 {
const VIRTIO_NET_S_LINK_UP = 1;
const VIRTIO_NET_S_ANNOUNCE = 2;
}
}
#[derive(Debug, Clone, Copy, Pod)]
#[repr(C)]
pub struct VirtioNetConfig {
pub mac: EthernetAddr,
pub status: Status,
max_virtqueue_pairs: u16,
mtu: u16,
speed: u32,
duplex: u8,
rss_max_key_size: u8,
rss_max_indirection_table_length: u16,
supported_hash_types: u32,
}
impl VirtioNetConfig {
pub(crate) fn new(cap: &CapabilityVirtioData, bars: [Option<BAR>; 6]) -> InFramePtr<Self> {
let bar = cap.bar;
let offset = cap.offset;
match bars[bar as usize].expect("Virtio pci net cfg:bar is none") {
BAR::Memory(address, _, _, _) => InFramePtr::new(address as usize + offset as usize)
.expect("can not get in frame ptr for virtio net config"),
BAR::IO(_, _) => panic!("Virtio pci net cfg:bar is IO type"),
}
}
}

View File

@ -0,0 +1,219 @@
use core::hint::spin_loop;
use alloc::vec::Vec;
use jinux_frame::offset_of;
use jinux_pci::{capability::vendor::virtio::CapabilityVirtioData, util::BAR};
use jinux_util::{frame_ptr::InFramePtr, slot_vec::SlotVec};
use log::debug;
use pod::Pod;
use crate::{
device::{network::config::NetworkFeatures, VirtioDeviceError},
queue::{QueueError, VirtQueue},
VirtioPciCommonCfg,
};
use super::{
buffer::{RxBuffer, TxBuffer},
config::VirtioNetConfig,
header::VirtioNetHdr,
};
#[derive(Debug, Clone, Copy, Pod)]
#[repr(C)]
pub struct EthernetAddr(pub [u8; 6]);
#[derive(Debug, Clone, Copy)]
pub enum VirtioNetError {
NotReady,
WrongToken,
Unknown,
}
pub struct NetworkDevice {
config: VirtioNetConfig,
mac_addr: EthernetAddr,
send_queue: VirtQueue,
recv_queue: VirtQueue,
rx_buffers: SlotVec<RxBuffer>,
}
impl From<QueueError> for VirtioNetError {
fn from(value: QueueError) -> Self {
match value {
QueueError::NotReady => VirtioNetError::NotReady,
QueueError::WrongToken => VirtioNetError::WrongToken,
_ => VirtioNetError::Unknown,
}
}
}
impl NetworkDevice {
pub(crate) fn negotiate_features(device_features: u64) -> u64 {
let device_features = NetworkFeatures::from_bits_truncate(device_features);
let supported_features = NetworkFeatures::support_features();
let network_features = device_features & supported_features;
debug!("{:?}", network_features);
network_features.bits()
}
pub fn new(
cap: &CapabilityVirtioData,
bars: [Option<BAR>; 6],
common_cfg: &InFramePtr<VirtioPciCommonCfg>,
notify_base_address: usize,
notify_off_multiplier: u32,
mut msix_vector_left: Vec<u16>,
) -> Result<Self, VirtioDeviceError> {
let virtio_net_config = VirtioNetConfig::new(cap, bars);
let features = {
// select low
common_cfg.write_at(
offset_of!(VirtioPciCommonCfg, device_feature_select),
0 as u32,
);
let device_feature_low =
common_cfg.read_at(offset_of!(VirtioPciCommonCfg, device_feature)) as u64;
// select high
common_cfg.write_at(
offset_of!(VirtioPciCommonCfg, device_feature_select),
1 as u32,
);
let device_feature_high =
common_cfg.read_at(offset_of!(VirtioPciCommonCfg, device_feature)) as u64;
let device_feature = device_feature_high << 32 | device_feature_low;
NetworkFeatures::from_bits_truncate(Self::negotiate_features(device_feature))
};
debug!("virtio_net_config = {:?}", virtio_net_config);
debug!("features = {:?}", features);
let mac_addr = virtio_net_config.read_at(offset_of!(VirtioNetConfig, mac));
let status = virtio_net_config.read_at(offset_of!(VirtioNetConfig, status));
debug!("mac addr = {:x?}, status = {:?}", mac_addr, status);
let (recv_msix_vec, send_msix_vec) = {
if msix_vector_left.len() >= 2 {
let vector1 = msix_vector_left.pop().unwrap();
let vector2 = msix_vector_left.pop().unwrap();
(vector1, vector2)
} else {
let vector = msix_vector_left.pop().unwrap();
(vector, vector)
}
};
let mut recv_queue = VirtQueue::new(
&common_cfg,
QUEUE_RECV as usize,
QUEUE_SIZE,
notify_base_address,
notify_off_multiplier,
recv_msix_vec,
)
.expect("creating recv queue fails");
let send_queue = VirtQueue::new(
&common_cfg,
QUEUE_SEND as usize,
QUEUE_SIZE,
notify_base_address,
notify_off_multiplier,
send_msix_vec,
)
.expect("create send queue fails");
let mut rx_buffers = SlotVec::new();
for i in 0..QUEUE_SIZE {
let mut rx_buffer = RxBuffer::new(RX_BUFFER_LEN);
let token = recv_queue.add(&[], &mut [rx_buffer.buf_mut()])?;
assert_eq!(i, token);
assert_eq!(rx_buffers.put(rx_buffer) as u16, i);
}
if recv_queue.should_notify() {
debug!("notify receive queue");
recv_queue.notify();
}
Ok(Self {
config: virtio_net_config.read(),
mac_addr,
send_queue,
recv_queue,
rx_buffers,
})
}
/// Add a rx buffer to recv queue
fn add_rx_buffer(&mut self, mut rx_buffer: RxBuffer) -> Result<(), VirtioNetError> {
let token = self.recv_queue.add(&[], &mut [rx_buffer.buf_mut()])?;
assert!(self.rx_buffers.put_at(token as usize, rx_buffer).is_none());
if self.recv_queue.should_notify() {
self.recv_queue.notify();
}
Ok(())
}
// Acknowledge interrupt
pub fn ack_interrupt(&self) -> bool {
todo!()
}
/// The mac address
pub fn mac_addr(&self) -> EthernetAddr {
self.mac_addr
}
/// Send queue is ready
pub fn can_send(&self) -> bool {
self.send_queue.available_desc() >= 2
}
/// Receive queue is ready
pub fn can_receive(&self) -> bool {
self.recv_queue.can_pop()
}
/// Receive a packet from network. If packet is ready, returns a RxBuffer containing the packet.
/// Otherwise, return NotReady error.
pub fn receive(&mut self) -> Result<RxBuffer, VirtioNetError> {
let (token, len) = self.recv_queue.pop_used()?;
debug!("receive packet: token = {}, len = {}", token, len);
let mut rx_buffer = self
.rx_buffers
.remove(token as usize)
.ok_or(VirtioNetError::WrongToken)?;
rx_buffer.set_packet_len(len as usize);
// FIXME: Ideally, we can reuse the returned buffer without creating new buffer.
// But this requires locking device to be compatible with smoltcp interface.
let new_rx_buffer = RxBuffer::new(RX_BUFFER_LEN);
self.add_rx_buffer(new_rx_buffer)?;
Ok(rx_buffer)
}
/// Send a packet to network. Return until the request completes.
pub fn send(&mut self, tx_buffer: TxBuffer) -> Result<(), VirtioNetError> {
let header = VirtioNetHdr::default();
let token = self
.send_queue
.add(&[header.as_bytes(), tx_buffer.buf()], &mut [])?;
if self.send_queue.should_notify() {
self.send_queue.notify();
}
// Wait until the buffer is used
while !self.send_queue.can_pop() {
spin_loop();
}
// Pop out the buffer, so we can reuse the send queue further
let (pop_token, _) = self.send_queue.pop_used()?;
debug_assert!(pop_token == token);
if pop_token != token {
return Err(VirtioNetError::WrongToken);
}
debug!("send packet succeeds");
Ok(())
}
}
const QUEUE_RECV: u16 = 0;
const QUEUE_SEND: u16 = 1;
const QUEUE_SIZE: u16 = 64;
const RX_BUFFER_LEN: usize = 4096;

View File

@ -0,0 +1,44 @@
use bitflags::bitflags;
use int_to_c_enum::TryFromInt;
use pod::Pod;
pub const VIRTIO_NET_HDR_LEN: usize = core::mem::size_of::<VirtioNetHdr>();
/// VirtioNet header precedes each packet
#[repr(C)]
#[derive(Default, Debug, Clone, Copy, Pod)]
pub struct VirtioNetHdr {
flags: Flags,
gso_type: u8,
hdr_len: u16,
gso_size: u16,
csum_start: u16,
csum_offset: u16,
num_buffers: u16, // Only if PCI is modern or VIRTIO_NET_F_MRG_RXBUF negotiated
// hash_value: u32, // Only if VIRTIO_NET_F_HASH_REPORT negotiated
// hash_report: u16, // Only if VIRTIO_NET_F_HASH_REPORT negotiated
// padding_reserved: u16, // Only if VIRTIO_NET_F_HASH_REPORT negotiated
}
bitflags! {
#[repr(C)]
#[derive(Default, Pod)]
pub struct Flags: u8 {
const VIRTIO_NET_HDR_F_NEEDS_CSUM = 1;
const VIRTIO_NET_HDR_F_DATA_VALID = 2;
const VIRTIO_NET_HDR_F_RSC_INFO = 4;
}
}
#[repr(u8)]
#[derive(Default, Debug, Clone, Copy, TryFromInt)]
#[allow(non_camel_case_types)]
pub enum GsoType {
#[default]
VIRTIO_NET_HDR_GSO_NONE = 0,
VIRTIO_NET_HDR_GSO_TCPV4 = 1,
VIRTIO_NET_HDR_GSO_UDP = 3,
VIRTIO_NET_HDR_GSO_TCPV6 = 4,
VIRTIO_NET_HDR_GSO_UDP_L4 = 5,
VIRTIO_NET_HDR_GSO_ECN = 0x80,
}

View File

@ -0,0 +1,4 @@
pub mod buffer;
pub mod config;
pub mod device;
pub mod header;

View File

@ -202,7 +202,7 @@ pub enum VirtioDeviceType {
impl VirtioDeviceType {
pub fn from_virtio_device(device: &VirtioDevice) -> Self {
match device {
VirtioDevice::Network => VirtioDeviceType::Network,
VirtioDevice::Network(_) => VirtioDeviceType::Network,
VirtioDevice::Block(_) => VirtioDeviceType::Block,
VirtioDevice::Console => VirtioDeviceType::Console,
VirtioDevice::Entropy => VirtioDeviceType::Entropy,