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

23
Cargo.lock generated
View File

@ -113,6 +113,12 @@ version = "1.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
[[package]]
name = "bytes"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be"
[[package]]
name = "cfg-if"
version = "1.0.0"
@ -473,6 +479,21 @@ dependencies = [
"virtio-input-decoder",
]
[[package]]
name = "jinux-network"
version = "0.1.0"
dependencies = [
"component",
"jinux-frame",
"jinux-pci",
"jinux-util",
"jinux-virtio",
"log",
"ringbuf",
"smoltcp",
"spin 0.9.4",
]
[[package]]
name = "jinux-pci"
version = "0.1.0"
@ -520,6 +541,7 @@ dependencies = [
"jinux-block",
"jinux-frame",
"jinux-input",
"jinux-network",
"jinux-rights",
"jinux-rights-proc",
"jinux-time",
@ -567,6 +589,7 @@ version = "0.1.0"
dependencies = [
"align_ext",
"bitflags",
"bytes",
"component",
"int-to-c-enum",
"jinux-frame",

View File

@ -27,6 +27,7 @@ members = [
"services/comps/virtio",
"services/comps/input",
"services/comps/block",
"services/comps/network",
"services/comps/framebuffer",
"services/comps/time",
"services/libs/jinux-std",

View File

@ -21,6 +21,12 @@ const COMMON_ARGS: &[&str] = &[
"virtio-blk-pci,bus=pcie.0,addr=0x6,drive=x0",
"-device",
"virtio-keyboard-pci",
"-device",
"virtio-net-pci,netdev=net01,disable-legacy=on,disable-modern=off",
"-netdev",
"user,id=net01,hostfwd=tcp::30022-:22,hostfwd=tcp::30080-:8080",
"-object",
"filter-dump,id=filter0,netdev=net01,file=virtio-net.pcap",
"-monitor",
"vc",
"-serial",

View File

@ -0,0 +1,17 @@
[package]
name = "jinux-network"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
component = { path = "../../libs/comp-sys/component" }
jinux-frame = { path = "../../../framework/jinux-frame" }
jinux-virtio = { path = "../virtio" }
jinux-util = { path = "../../libs/jinux-util" }
jinux-pci = { path = "../pci" }
spin = "0.9.4"
ringbuf = { version = "0.3.2", default-features = false, features = ["alloc"] }
log = "0.4"
smoltcp = { version = "0.9.1", default-features = false, features = ["alloc", "log", "medium-ethernet", "medium-ip", "proto-dhcpv4", "proto-ipv4", "proto-igmp", "socket-icmp", "socket-udp", "socket-tcp", "socket-raw", "socket-dhcpv4"] }

View File

@ -0,0 +1,71 @@
use alloc::vec;
use smoltcp::phy::{self, Medium};
use crate::VirtioNet;
use jinux_virtio::device::network::{
buffer::{RxBuffer, TxBuffer},
device::NetworkDevice,
};
impl phy::Device for VirtioNet {
type RxToken<'a> = RxToken;
type TxToken<'a> = TxToken<'a>;
fn receive(
&mut self,
_timestamp: smoltcp::time::Instant,
) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)> {
if self.can_receive() {
let device = self.device_mut();
let rx_buffer = device.receive().unwrap();
Some((RxToken(rx_buffer), TxToken(device)))
} else {
None
}
}
fn transmit(&mut self, _timestamp: smoltcp::time::Instant) -> Option<Self::TxToken<'_>> {
if self.can_send() {
let device = self.device_mut();
Some(TxToken(device))
} else {
None
}
}
fn capabilities(&self) -> phy::DeviceCapabilities {
let mut caps = phy::DeviceCapabilities::default();
caps.max_transmission_unit = 1536;
caps.max_burst_size = Some(1);
caps.medium = Medium::Ethernet;
caps
}
}
pub struct RxToken(RxBuffer);
impl phy::RxToken for RxToken {
fn consume<R, F>(mut self, f: F) -> R
where
F: FnOnce(&mut [u8]) -> R,
{
let packet_but = self.0.packet_mut();
let res = f(packet_but);
res
}
}
pub struct TxToken<'a>(&'a mut NetworkDevice);
impl<'a> phy::TxToken for TxToken<'a> {
fn consume<R, F>(self, len: usize, f: F) -> R
where
F: FnOnce(&mut [u8]) -> R,
{
let mut buffer = vec![0u8; len];
let res = f(&mut buffer);
let tx_buffer = TxBuffer::new(&buffer);
self.0.send(tx_buffer).expect("Send packet failed");
res
}
}

View File

@ -0,0 +1,59 @@
#![no_std]
#![forbid(unsafe_code)]
#![feature(trait_alias)]
extern crate alloc;
use alloc::sync::Arc;
use alloc::vec::Vec;
use component::init_component;
use component::ComponentInitError;
use core::any::Any;
use jinux_frame::sync::SpinLock;
use jinux_virtio::device::network::device::EthernetAddr;
use jinux_virtio::VirtioDeviceType;
use spin::Once;
mod driver;
mod virtio;
pub use virtio::VirtioNet;
pub trait NetworkDevice: Send + Sync + Any {
fn irq_number(&self) -> u8;
fn name(&self) -> &'static str;
fn mac_addr(&self) -> EthernetAddr;
}
pub trait NetDeviceIrqHandler = Fn(u8) + Send + Sync + 'static;
pub(crate) static NETWORK_IRQ_HANDLERS: Once<SpinLock<Vec<Arc<dyn NetDeviceIrqHandler>>>> =
Once::new();
#[init_component]
fn init() -> Result<(), ComponentInitError> {
NETWORK_IRQ_HANDLERS.call_once(|| SpinLock::new(Vec::new()));
Ok(())
}
pub fn probe_virtio_net() -> Result<VirtioNet, ComponentInitError> {
let network_devices = {
let virtio = jinux_virtio::VIRTIO_COMPONENT.get().unwrap();
virtio.get_device(VirtioDeviceType::Network)
};
for device in network_devices {
let virtio_net = VirtioNet::new(device);
// FIXME: deal with multiple net devices
return Ok(virtio_net);
}
Err(ComponentInitError::Unknown)
}
pub fn register_net_device_irq_handler(callback: impl NetDeviceIrqHandler) {
NETWORK_IRQ_HANDLERS
.get()
.unwrap()
.lock()
.push(Arc::new(callback))
}

View File

@ -0,0 +1,103 @@
use jinux_frame::offset_of;
use jinux_frame::sync::SpinLock;
use jinux_frame::trap::TrapFrame;
use jinux_pci::msix::MSIX;
use jinux_util::frame_ptr::InFramePtr;
use jinux_virtio::device::network::device::{self, EthernetAddr};
use jinux_virtio::PCIVirtioDevice;
use jinux_virtio::VirtioPciCommonCfg;
use log::debug;
use crate::{NetworkDevice, NETWORK_IRQ_HANDLERS};
pub struct VirtioNet {
/// Network Device
device: device::NetworkDevice,
/// Own common cfg to avoid other devices access this frame
_common_cfg: InFramePtr<VirtioPciCommonCfg>,
_msix: SpinLock<MSIX>,
irq_number: u8,
}
impl NetworkDevice for VirtioNet {
fn irq_number(&self) -> u8 {
self.irq_number
}
fn name(&self) -> &'static str {
"virtio net"
}
fn mac_addr(&self) -> EthernetAddr {
self.device.mac_addr()
}
}
impl VirtioNet {
pub(crate) fn new(virtio_device: PCIVirtioDevice) -> Self {
let device = if let jinux_virtio::device::VirtioDevice::Network(network_device) =
virtio_device.device
{
network_device
} else {
panic!("Invalid device type")
};
let common_cfg = virtio_device.common_cfg;
let mut msix = virtio_device.msix;
let config_msix_vector =
common_cfg.read_at(offset_of!(VirtioPciCommonCfg, config_msix_vector)) as usize;
let mut network_irq_num = 0;
for i in 0..msix.table_size as usize {
let msix_entry = msix.table.get_mut(i).unwrap();
if !msix_entry.irq_handle.is_empty() {
panic!("msix already have irq functions");
}
if config_msix_vector == i {
debug!(
"network config space change irq number = {}",
msix_entry.irq_handle.num()
);
msix_entry.irq_handle.on_active(config_space_change);
} else {
network_irq_num = msix_entry.irq_handle.num();
msix_entry.irq_handle.on_active(handle_network_event);
}
}
debug_assert!(network_irq_num != 0);
debug!("Network device irq num = {}", network_irq_num);
let device = VirtioNet {
device,
_common_cfg: common_cfg,
irq_number: network_irq_num,
_msix: SpinLock::new(msix),
};
device
}
pub(crate) fn can_receive(&self) -> bool {
self.device.can_receive()
}
pub(crate) fn can_send(&self) -> bool {
self.device.can_send()
}
pub(crate) fn device_mut(&mut self) -> &mut device::NetworkDevice {
&mut self.device
}
}
/// Interrupt handler if network device config space changes
fn config_space_change(_: &TrapFrame) {
debug!("network device config space change");
}
/// Interrupt handler if network device receives some packet
fn handle_network_event(trap_frame: &TrapFrame) {
let irq_num = trap_frame.trap_num as u8;
for callback in NETWORK_IRQ_HANDLERS.get().unwrap().lock().iter() {
callback(irq_num);
}
}

View File

@ -8,6 +8,7 @@ edition = "2021"
[dependencies]
bitflags = "1.3"
spin = "0.9.4"
bytes = { version = "1.4.0", default-features = false }
align_ext = { path = "../../../framework/libs/align_ext" }
jinux-frame = { path = "../../../framework/jinux-frame" }
jinux-pci = { path = "../pci" }

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,

View File

@ -11,6 +11,7 @@ align_ext = { path = "../../../framework/libs/align_ext" }
pod = { git = "https://github.com/jinzhao-dev/pod", rev = "7fa2ed2" }
jinux-input = { path = "../../comps/input" }
jinux-block = { path = "../../comps/block" }
jinux-network = { path = "../../comps/network" }
jinux-time = { path = "../../comps/time" }
jinux-rights = { path = "../jinux-rights" }
controlled = { path = "../../libs/comp-sys/controlled" }

View File

@ -17,8 +17,8 @@ use super::{
};
pub struct IfaceCommon {
interface: Mutex<smoltcp::iface::Interface>,
sockets: Mutex<SocketSet<'static>>,
interface: SpinLock<smoltcp::iface::Interface>,
sockets: SpinLock<SocketSet<'static>>,
used_ports: RwLock<BTreeMap<u16, usize>>,
/// The time should do next poll. We stores the total microseconds since system boots up.
next_poll_at_ms: AtomicU64,
@ -30,19 +30,19 @@ impl IfaceCommon {
let socket_set = SocketSet::new(Vec::new());
let used_ports = BTreeMap::new();
Self {
interface: Mutex::new(interface),
sockets: Mutex::new(socket_set),
interface: SpinLock::new(interface),
sockets: SpinLock::new(socket_set),
used_ports: RwLock::new(used_ports),
next_poll_at_ms: AtomicU64::new(0),
bound_sockets: RwLock::new(BTreeSet::new()),
}
}
pub(super) fn interface(&self) -> MutexGuard<smoltcp::iface::Interface> {
pub(super) fn interface(&self) -> SpinLockGuard<smoltcp::iface::Interface> {
self.interface.lock()
}
pub(super) fn sockets(&self) -> MutexGuard<smoltcp::iface::SocketSet<'static>> {
pub(super) fn sockets(&self) -> SpinLockGuard<smoltcp::iface::SocketSet<'static>> {
self.sockets.lock()
}

View File

@ -7,11 +7,13 @@ mod common;
mod loopback;
mod time;
mod util;
mod virtio;
pub use any_socket::{AnyBoundSocket, AnyUnboundSocket, RawTcpSocket, RawUdpSocket};
pub use loopback::IfaceLoopback;
pub use smoltcp::wire::{EthernetAddress, IpAddress, IpEndpoint, IpListenEndpoint, Ipv4Address};
pub use util::{spawn_background_poll_thread, BindPortConfig};
pub use virtio::IfaceVirtio;
/// Network interface.
///
@ -63,11 +65,11 @@ mod internal {
pub trait IfaceInternal {
fn common(&self) -> &IfaceCommon;
/// The inner socket set
fn sockets(&self) -> MutexGuard<SocketSet<'static>> {
fn sockets(&self) -> SpinLockGuard<SocketSet<'static>> {
self.common().sockets()
}
/// The inner iface.
fn iface_inner(&self) -> MutexGuard<smoltcp::iface::Interface> {
fn iface_inner(&self) -> SpinLockGuard<smoltcp::iface::Interface> {
self.common().interface()
}
/// The time we should do another poll.

View File

@ -0,0 +1,125 @@
use crate::prelude::*;
use jinux_frame::sync::SpinLock;
use jinux_network::{probe_virtio_net, NetworkDevice, VirtioNet};
use smoltcp::{
iface::{Config, Routes, SocketHandle, SocketSet},
socket::dhcpv4,
wire::{self, IpCidr},
};
use super::{common::IfaceCommon, internal::IfaceInternal, Iface};
pub struct IfaceVirtio {
driver: SpinLock<VirtioNet>,
common: IfaceCommon,
dhcp_handle: SocketHandle,
weak_self: Weak<Self>,
}
impl IfaceVirtio {
pub fn new() -> Arc<Self> {
let mut virtio_net = probe_virtio_net().unwrap();
let interface = {
let mac_addr = virtio_net.mac_addr();
let ip_addr = IpCidr::new(wire::IpAddress::Ipv4(wire::Ipv4Address::UNSPECIFIED), 0);
let routes = Routes::new();
let config = {
let mut config = Config::new();
config.hardware_addr = Some(wire::HardwareAddress::Ethernet(
wire::EthernetAddress(mac_addr.0),
));
config
};
let mut interface = smoltcp::iface::Interface::new(config, &mut virtio_net);
interface.update_ip_addrs(|ip_addrs| {
debug_assert!(ip_addrs.len() == 0);
ip_addrs.push(ip_addr).unwrap();
});
interface
};
let common = IfaceCommon::new(interface);
let mut socket_set = common.sockets();
let dhcp_handle = init_dhcp_client(&mut socket_set);
drop(socket_set);
Arc::new_cyclic(|weak| Self {
driver: SpinLock::new(virtio_net),
common,
dhcp_handle,
weak_self: weak.clone(),
})
}
/// FIXME: Once we have user program dhcp client, we may remove dhcp logic from kernel.
pub fn process_dhcp(&self) {
let mut socket_set = self.common.sockets();
let dhcp_socket: &mut dhcpv4::Socket = socket_set.get_mut(self.dhcp_handle);
let config = if let Some(event) = dhcp_socket.poll() {
debug!("event = {:?}", event);
if let dhcpv4::Event::Configured(config) = event {
config
} else {
return;
}
} else {
return;
};
let ip_addr = IpCidr::Ipv4(config.address);
let mut interface = self.common.interface();
interface.update_ip_addrs(|ipaddrs| {
if let Some(addr) = ipaddrs.iter_mut().next() {
// already has ipaddrs
*addr = ip_addr
} else {
// does not has ip addr
ipaddrs.push(ip_addr).unwrap();
}
});
println!(
"DHCP update IP address: {:?}",
interface.ipv4_addr().unwrap()
);
if let Some(router) = config.router {
println!("Default router address: {:?}", router);
interface
.routes_mut()
.add_default_ipv4_route(router)
.unwrap();
}
}
}
impl IfaceInternal for IfaceVirtio {
fn common(&self) -> &IfaceCommon {
&self.common
}
fn arc_self(&self) -> Arc<dyn Iface> {
self.weak_self.upgrade().unwrap()
}
}
impl Iface for IfaceVirtio {
fn name(&self) -> &str {
"virtio"
}
fn mac_addr(&self) -> Option<smoltcp::wire::EthernetAddress> {
let interface = self.common.interface();
let hardware_addr = interface.hardware_addr();
match hardware_addr {
wire::HardwareAddress::Ethernet(ethe_address) => Some(ethe_address),
}
}
fn poll(&self) {
let mut driver = self.driver.lock();
self.common.poll(&mut *driver);
self.process_dhcp();
}
}
/// Register a dhcp socket.
fn init_dhcp_client(socket_set: &mut SocketSet) -> SocketHandle {
let dhcp_socket = dhcpv4::Socket::new();
socket_set.add(dhcp_socket)
}

View File

@ -1,7 +1,8 @@
use crate::{
net::iface::{Iface, IfaceLoopback},
net::iface::{Iface, IfaceLoopback, IfaceVirtio},
prelude::*,
};
use jinux_network::register_net_device_irq_handler;
use spin::Once;
use self::iface::spawn_background_poll_thread;
@ -13,8 +14,15 @@ pub mod socket;
pub fn init() {
IFACES.call_once(|| {
let iface_virtio = IfaceVirtio::new();
let iface_loopback = IfaceLoopback::new();
vec![iface_loopback]
vec![iface_virtio, iface_loopback]
});
register_net_device_irq_handler(|irq_num| {
debug!("irq num = {}", irq_num);
// TODO: further check that the irq num is the same as iface's irq num
let iface_virtio = &IFACES.get().unwrap()[0];
iface_virtio.poll();
});
poll_ifaces();
}