diff --git a/Cargo.lock b/Cargo.lock index 693a9ec5..d6989f93 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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", diff --git a/Cargo.toml b/Cargo.toml index 66fb0182..1a20aea9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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", diff --git a/boot/src/main.rs b/boot/src/main.rs index 2c621271..dc73e560 100644 --- a/boot/src/main.rs +++ b/boot/src/main.rs @@ -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", diff --git a/services/comps/network/Cargo.toml b/services/comps/network/Cargo.toml new file mode 100644 index 00000000..a39d84da --- /dev/null +++ b/services/comps/network/Cargo.toml @@ -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"] } \ No newline at end of file diff --git a/services/comps/network/src/driver.rs b/services/comps/network/src/driver.rs new file mode 100644 index 00000000..707810bb --- /dev/null +++ b/services/comps/network/src/driver.rs @@ -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> { + 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(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(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 + } +} diff --git a/services/comps/network/src/lib.rs b/services/comps/network/src/lib.rs new file mode 100644 index 00000000..c67f2b5e --- /dev/null +++ b/services/comps/network/src/lib.rs @@ -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>>> = + Once::new(); + +#[init_component] +fn init() -> Result<(), ComponentInitError> { + NETWORK_IRQ_HANDLERS.call_once(|| SpinLock::new(Vec::new())); + Ok(()) +} + +pub fn probe_virtio_net() -> Result { + 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)) +} diff --git a/services/comps/network/src/virtio.rs b/services/comps/network/src/virtio.rs new file mode 100644 index 00000000..e97a531a --- /dev/null +++ b/services/comps/network/src/virtio.rs @@ -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, + _msix: SpinLock, + 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); + } +} diff --git a/services/comps/virtio/Cargo.toml b/services/comps/virtio/Cargo.toml index 0dd5ccc1..7bfdfb60 100644 --- a/services/comps/virtio/Cargo.toml +++ b/services/comps/virtio/Cargo.toml @@ -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" } diff --git a/services/comps/virtio/src/device/mod.rs b/services/comps/virtio/src/device/mod.rs index 385fd41c..cf007e68 100644 --- a/services/comps/virtio/src/device/mod.rs +++ b/services/comps/virtio/src/device/mod.rs @@ -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!(), diff --git a/services/comps/virtio/src/device/network/buffer.rs b/services/comps/virtio/src/device/network/buffer.rs new file mode 100644 index 00000000..3e73be24 --- /dev/null +++ b/services/comps/virtio/src/device/network/buffer.rs @@ -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 + } +} diff --git a/services/comps/virtio/src/device/network/config.rs b/services/comps/virtio/src/device/network/config.rs new file mode 100644 index 00000000..36356573 --- /dev/null +++ b/services/comps/virtio/src/device/network/config.rs @@ -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; 6]) -> InFramePtr { + 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"), + } + } +} diff --git a/services/comps/virtio/src/device/network/device.rs b/services/comps/virtio/src/device/network/device.rs new file mode 100644 index 00000000..10c36ce2 --- /dev/null +++ b/services/comps/virtio/src/device/network/device.rs @@ -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, +} + +impl From 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; 6], + common_cfg: &InFramePtr, + notify_base_address: usize, + notify_off_multiplier: u32, + mut msix_vector_left: Vec, + ) -> Result { + 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 { + 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; diff --git a/services/comps/virtio/src/device/network/header.rs b/services/comps/virtio/src/device/network/header.rs new file mode 100644 index 00000000..c1179ab3 --- /dev/null +++ b/services/comps/virtio/src/device/network/header.rs @@ -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::(); + +/// 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, +} diff --git a/services/comps/virtio/src/device/network/mod.rs b/services/comps/virtio/src/device/network/mod.rs new file mode 100644 index 00000000..0e163938 --- /dev/null +++ b/services/comps/virtio/src/device/network/mod.rs @@ -0,0 +1,4 @@ +pub mod buffer; +pub mod config; +pub mod device; +pub mod header; diff --git a/services/comps/virtio/src/lib.rs b/services/comps/virtio/src/lib.rs index f94a6b92..4681819f 100644 --- a/services/comps/virtio/src/lib.rs +++ b/services/comps/virtio/src/lib.rs @@ -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, diff --git a/services/libs/jinux-std/Cargo.toml b/services/libs/jinux-std/Cargo.toml index 8d94b029..4f07bc33 100644 --- a/services/libs/jinux-std/Cargo.toml +++ b/services/libs/jinux-std/Cargo.toml @@ -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" } diff --git a/services/libs/jinux-std/src/net/iface/common.rs b/services/libs/jinux-std/src/net/iface/common.rs index e120fbd8..2a9415b1 100644 --- a/services/libs/jinux-std/src/net/iface/common.rs +++ b/services/libs/jinux-std/src/net/iface/common.rs @@ -17,8 +17,8 @@ use super::{ }; pub struct IfaceCommon { - interface: Mutex, - sockets: Mutex>, + interface: SpinLock, + sockets: SpinLock>, used_ports: RwLock>, /// 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 { + pub(super) fn interface(&self) -> SpinLockGuard { self.interface.lock() } - pub(super) fn sockets(&self) -> MutexGuard> { + pub(super) fn sockets(&self) -> SpinLockGuard> { self.sockets.lock() } diff --git a/services/libs/jinux-std/src/net/iface/mod.rs b/services/libs/jinux-std/src/net/iface/mod.rs index 7298f1bf..04fb0b4b 100644 --- a/services/libs/jinux-std/src/net/iface/mod.rs +++ b/services/libs/jinux-std/src/net/iface/mod.rs @@ -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> { + fn sockets(&self) -> SpinLockGuard> { self.common().sockets() } /// The inner iface. - fn iface_inner(&self) -> MutexGuard { + fn iface_inner(&self) -> SpinLockGuard { self.common().interface() } /// The time we should do another poll. diff --git a/services/libs/jinux-std/src/net/iface/virtio.rs b/services/libs/jinux-std/src/net/iface/virtio.rs new file mode 100644 index 00000000..02e3e0cc --- /dev/null +++ b/services/libs/jinux-std/src/net/iface/virtio.rs @@ -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, + common: IfaceCommon, + dhcp_handle: SocketHandle, + weak_self: Weak, +} + +impl IfaceVirtio { + pub fn new() -> Arc { + 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 { + self.weak_self.upgrade().unwrap() + } +} + +impl Iface for IfaceVirtio { + fn name(&self) -> &str { + "virtio" + } + + fn mac_addr(&self) -> Option { + 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) +} diff --git a/services/libs/jinux-std/src/net/mod.rs b/services/libs/jinux-std/src/net/mod.rs index db3af95d..e5caccff 100644 --- a/services/libs/jinux-std/src/net/mod.rs +++ b/services/libs/jinux-std/src/net/mod.rs @@ -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(); }