Add virtio net device driver
This commit is contained in:
parent
2985cdced6
commit
7304e06c88
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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"] }
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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))
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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" }
|
||||
|
|
|
@ -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!(),
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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"),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
|
@ -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,
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
pub mod buffer;
|
||||
pub mod config;
|
||||
pub mod device;
|
||||
pub mod header;
|
|
@ -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,
|
||||
|
|
|
@ -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" }
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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)
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue