diff --git a/Cargo.lock b/Cargo.lock index 9632e288..6a6be671 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1048,6 +1048,38 @@ dependencies = [ "winapi", ] +[[package]] +name = "defmt" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86f6162c53f659f65d00619fe31f14556a6e9f8752ccc4a41bd177ffcf3d6130" +dependencies = [ + "bitflags 1.3.2", + "defmt-macros", +] + +[[package]] +name = "defmt-macros" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d135dd939bad62d7490b0002602d35b358dce5fd9233a709d3c1ef467d4bde6" +dependencies = [ + "defmt-parser", + "proc-macro-error2", + "proc-macro2", + "quote", + "syn 2.0.90", +] + +[[package]] +name = "defmt-parser" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3983b127f13995e68c1e29071e5d115cd96f215ccb5e6812e3728cd6f92653b3" +dependencies = [ + "thiserror 2.0.8", +] + [[package]] name = "der" version = "0.7.9" @@ -1194,6 +1226,17 @@ dependencies = [ "cfg-if 1.0.0", ] +[[package]] +name = "enumn" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f9ed6b3789237c8a0c1c505af1c7eb2c560df6186f01b098c3a1064ea532f38" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.90", +] + [[package]] name = "equivalent" version = "1.0.1" @@ -1385,6 +1428,12 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "fragile" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c2141d6d6c8512188a7891b4b01590a45f6dac67afb4f255c4124dbb86d4eaa" + [[package]] name = "fs_extra" version = "1.3.0" @@ -2414,6 +2463,15 @@ dependencies = [ "thread_local", ] +[[package]] +name = "hash32" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47d60b12902ba28e2730cd37e95b8c9223af2808df9e902d4df49588d1470606" +dependencies = [ + "byteorder", +] + [[package]] name = "hashbrown" version = "0.12.3" @@ -2450,6 +2508,16 @@ dependencies = [ "hashbrown 0.14.5", ] +[[package]] +name = "heapless" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bfb9eb618601c89945a70e254898da93b13be0388091d42117462b265bb3fad" +dependencies = [ + "hash32", + "stable_deref_trait", +] + [[package]] name = "heck" version = "0.4.1" @@ -3064,9 +3132,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.20" +version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" [[package]] name = "logboi" @@ -3104,6 +3172,12 @@ dependencies = [ "hashbrown 0.15.0", ] +[[package]] +name = "managed" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ca88d725a0a943b096803bd34e73a4437208b6077654cc4ecb2947a5f91618d" + [[package]] name = "matchers" version = "0.1.0" @@ -3862,6 +3936,28 @@ dependencies = [ "version_check", ] +[[package]] +name = "proc-macro-error-attr2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] +name = "proc-macro-error2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" +dependencies = [ + "proc-macro-error-attr2", + "proc-macro2", + "quote", + "syn 2.0.90", +] + [[package]] name = "proc-macro2" version = "1.0.92" @@ -4411,6 +4507,22 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "617d17f088ec733e5a6b86da6ce4cce1414e6e856d6061c16dda51cceae6f68c" +[[package]] +name = "smoltcp" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a1a996951e50b5971a2c8c0fa05a381480d70a933064245c4a223ddc87ccc97" +dependencies = [ + "bitflags 1.3.2", + "byteorder", + "cfg-if 1.0.0", + "defmt", + "heapless", + "libc", + "log", + "managed", +] + [[package]] name = "socket2" version = "0.4.10" @@ -5227,6 +5339,39 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +[[package]] +name = "virtio" +version = "0.1.0" +dependencies = [ + "smoltcp", + "virtio-net", +] + +[[package]] +name = "virtio-drivers" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6a39747311dabb3d37807037ed1c3c38d39f99198d091b5b79ecd5c8d82f799" +dependencies = [ + "bitflags 2.6.0", + "enumn", + "log", + "zerocopy", +] + +[[package]] +name = "virtio-net" +version = "0.1.0" +dependencies = [ + "fragile", + "once_cell", + "smoltcp", + "twizzler-abi", + "twizzler-driver", + "virtio-drivers", + "volatile", +] + [[package]] name = "volatile" version = "0.5.1" diff --git a/Cargo.toml b/Cargo.toml index 3208b7a5..274f636a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,6 +12,7 @@ members = [ "src/bin/pager", "src/bin/mnemosyne", "src/bin/stdfs_demo", + "src/bin/virtio", "src/kernel", "src/lib/twizzler-queue-raw", "src/lib/twizzler-queue", @@ -32,7 +33,8 @@ members = [ "src/abi/types", "src/lib/logboi", "src/srv/logboi-srv", - "src/bin/logboi-test", + "src/bin/logboi-test", + "src/lib/virtio-net", ] exclude = ["toolchain/src/rust"] @@ -44,6 +46,7 @@ initrd = [ "crate:init", "crate:devmgr", "crate:pager", + "crate:virtio", "lib:twz-rt", "crate:monitor", "crate:montest", diff --git a/src/bin/virtio/Cargo.toml b/src/bin/virtio/Cargo.toml new file mode 100644 index 00000000..512dec83 --- /dev/null +++ b/src/bin/virtio/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "virtio" +version = "0.1.0" +edition = "2021" + +[dependencies] + +virtio-net = { path = "../../lib/virtio-net" } + +[dependencies.smoltcp] +version = "0.11.0" +optional = false +default-features = true +features = [ + "alloc", "log", + "medium-ethernet", + "proto-ipv4", + "socket-raw", "socket-icmp", "socket-udp", "socket-tcp", +] \ No newline at end of file diff --git a/src/bin/virtio/src/main.rs b/src/bin/virtio/src/main.rs new file mode 100644 index 00000000..d384173c --- /dev/null +++ b/src/bin/virtio/src/main.rs @@ -0,0 +1,100 @@ +use core::{cell::RefCell, str::FromStr}; +use std::{borrow::ToOwned, rc::Rc, vec, vec::Vec}; + +use smoltcp::{ + iface::{Config, Interface, SocketSet}, + phy::{Device, DeviceCapabilities, Medium}, + socket::tcp, + time::Instant, + wire::{EthernetAddress, HardwareAddress, IpAddress, IpCidr, Ipv4Address}, +}; +use virtio_net::{get_device, DeviceWrapper}; + +const IP: &str = "10.0.2.15"; // QEMU user networking default IP +const GATEWAY: &str = "10.0.2.2"; // QEMU user networking gateway +const PORT: u16 = 5555; + +fn main() { + test_echo_server(); +} + +fn test_echo_server() { + let mut device = get_device(); + + if device.capabilities().medium != Medium::Ethernet { + panic!("This implementation only supports virtio-net which is an ethernet device"); + } + + let hardware_addr = HardwareAddress::Ethernet(device.mac_address()); + + // Create interface + let mut config = Config::new(hardware_addr); + config.random_seed = 0x2333; + + let mut iface = Interface::new(config, &mut device, Instant::now()); + iface.update_ip_addrs(|ip_addrs| { + ip_addrs + .push(IpCidr::new(IpAddress::from_str(IP).unwrap(), 24)) + .unwrap(); + }); + + iface + .routes_mut() + .add_default_ipv4_route(Ipv4Address::from_str(GATEWAY).unwrap()) + .unwrap(); + + // Create sockets + let tcp_rx_buffer = tcp::SocketBuffer::new(vec![0; 1024]); + let tcp_tx_buffer = tcp::SocketBuffer::new(vec![0; 1024]); + let tcp_socket = tcp::Socket::new(tcp_rx_buffer, tcp_tx_buffer); + + let mut sockets = SocketSet::new(vec![]); + let tcp_handle = sockets.add(tcp_socket); + + println!("start a echo server..."); + let mut tcp_active = false; + loop { + let timestamp = Instant::now(); + + iface.poll(timestamp, &mut device, &mut sockets); + + let socket = sockets.get_mut::(tcp_handle); + if !socket.is_open() { + println!("listening on port {}...", PORT); + socket.listen(PORT).unwrap(); + } + + if socket.is_active() && !tcp_active { + println!("tcp:{} connected", PORT); + } else if !socket.is_active() && tcp_active { + println!("tcp:{} disconnected", PORT); + } + tcp_active = socket.is_active(); + + if socket.may_recv() { + let data = socket + .recv(|buffer| { + let recvd_len = buffer.len(); + if !buffer.is_empty() { + println!("tcp:{} recv {} bytes: {:?}", PORT, recvd_len, buffer); + let lines = buffer + .split(|&b| b == b'\n') + .map(ToOwned::to_owned) + .collect::>(); + let data = lines.join(&b'\n'); + (recvd_len, data) + } else { + (0, vec![]) + } + }) + .unwrap(); + if socket.can_send() && !data.is_empty() { + println!("tcp:{} send data: {:?}", PORT, data); + socket.send_slice(&data[..]).unwrap(); + } + } else if socket.may_send() { + println!("tcp:{} close", PORT); + socket.close(); + } + } +} diff --git a/src/kernel/src/interrupt.rs b/src/kernel/src/interrupt.rs index 66480fa8..090c1cfd 100644 --- a/src/kernel/src/interrupt.rs +++ b/src/kernel/src/interrupt.rs @@ -147,7 +147,6 @@ pub fn handle_interrupt(number: u32) { let gi = get_global_interrupts(); gi.ints[number as usize].raise(); if number != 43 { - //logln!("external device interrupt {}", number); } } @@ -174,7 +173,6 @@ impl InterruptQueue { fn enqueue(&mut self, int: u32) { if self.is_full() { // TODO: extend this mechanism to avoid dropping interrupts - logln!("dropped interrupt {}", int); return; } self.queue[self.head] = int; diff --git a/src/lib/twizzler-driver/src/bus/pcie.rs b/src/lib/twizzler-driver/src/bus/pcie.rs index a5503530..8a7bb2ad 100644 --- a/src/lib/twizzler-driver/src/bus/pcie.rs +++ b/src/lib/twizzler-driver/src/bus/pcie.rs @@ -75,6 +75,7 @@ pub enum PcieCapability<'a> { Unknown(u8), Msi(VolatileRef<'a, MsiCapability>), MsiX(VolatileRef<'a, MsixCapability>), + VendorSpecific(usize), //Offset to the capability, as vendor specific capabilities do not have a consistent definition. } impl<'a> Iterator for PcieCapabilityIterator<'a> { @@ -92,6 +93,7 @@ impl<'a> Iterator for PcieCapabilityIterator<'a> { 0x11 => { PcieCapability::MsiX(self.cfg.get_mmio_offset_mut::(self.off)) } + 9 => PcieCapability::VendorSpecific(self.off), x => PcieCapability::Unknown(x), }; self.off = (map_field!(cap.next).read() & 0xfc) as usize; @@ -109,7 +111,7 @@ fn calc_msg_info(vec: InterruptVector, level: bool) -> (u64, u32) { } impl Device { - fn pcie_capabilities<'a>(&'a self, mm: &'a MmioObject) -> Option> { + pub fn pcie_capabilities<'a>(&'a self, mm: &'a MmioObject) -> Option> { let cfg = unsafe { mm.get_mmio_offset::(0) }; let cfg = cfg.as_ptr(); let ptr = map_field!(cfg.cap_ptr).read() & 0xfc; @@ -124,7 +126,7 @@ impl Device { }) } - fn find_mmio_bar(&self, bar: usize) -> Option { + pub fn find_mmio_bar(&self, bar: usize) -> Option { let mut idx = 0; while let Some(mm) = self.get_mmio(idx) { if mm.get_info().info == bar as u64 { diff --git a/src/lib/virtio-net/Cargo.toml b/src/lib/virtio-net/Cargo.toml new file mode 100644 index 00000000..3f331a93 --- /dev/null +++ b/src/lib/virtio-net/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "virtio-net" +version = "0.1.0" +edition = "2021" + +[dependencies] +twizzler-driver = { path = "../../lib/twizzler-driver" } +twizzler-abi = { path = "../../lib/twizzler-abi" } + +virtio-drivers = "0.7.5" +volatile = { version = "0.5", features = ["unstable"] } +once_cell = "1.19.0" +fragile = "2.0.0" + +[dependencies.smoltcp] +version = "0.11.0" +optional = false +default-features = true +features = [ + "alloc", "log", + "medium-ethernet", + "proto-ipv4", + "socket-raw", "socket-icmp", "socket-udp", "socket-tcp", +] \ No newline at end of file diff --git a/src/lib/virtio-net/src/hal.rs b/src/lib/virtio-net/src/hal.rs new file mode 100644 index 00000000..2991b570 --- /dev/null +++ b/src/lib/virtio-net/src/hal.rs @@ -0,0 +1,170 @@ +use core::ptr::NonNull; +use std::{collections::HashMap, ptr::copy_nonoverlapping, sync::Mutex}; + +use fragile::Fragile; +use once_cell::sync::OnceCell; +use twizzler_driver::dma::{Access, DmaOptions, DmaPool, DmaSliceRegion, SyncMode, DMA_PAGE_SIZE}; +use virtio_drivers::{BufferDirection, Hal, PhysAddr}; + +pub struct TestHal; + +static DMA_POOL_HOST_TO_DEVICE: OnceCell = OnceCell::new(); +static DMA_POOL_DEVICE_TO_HOST: OnceCell = OnceCell::new(); +static DMA_POOL_BIDIRECTIONAL: OnceCell = OnceCell::new(); + +// The DmaSliceRegions contained within this hashmap are never operated upon after being inserted +// into this hashmap, only the memory beneath it is. This hashmap is used to keep memory allocated +// while it is still in use. Fragile type only allows the thread that original thread that created +// the object to call its destructor. +static ALLOCED: OnceCell>>>> = OnceCell::new(); + +// Gets the global dma pool for the HAL in a given access direction. If it doesn't exist, create it. +fn get_dma_pool(dir: BufferDirection) -> &'static DmaPool { + match dir { + BufferDirection::DriverToDevice => match DMA_POOL_HOST_TO_DEVICE.get() { + Some(pool) => pool, + None => { + let pool = DmaPool::new( + DmaPool::default_spec(), + Access::HostToDevice, + DmaOptions::empty(), + ); + match DMA_POOL_HOST_TO_DEVICE.set(pool) { + Ok(_) => {} + Err(_) => panic!("Failed to set DMA_POOL_HOST_TO_DEVICE"), + } + match DMA_POOL_HOST_TO_DEVICE.get() { + Some(pool) => pool, + None => panic!("Failed to set DMA_POOL_HOST_TO_DEVICE"), + } + } + }, + BufferDirection::DeviceToDriver => match DMA_POOL_DEVICE_TO_HOST.get() { + Some(pool) => pool, + None => { + let pool = DmaPool::new( + DmaPool::default_spec(), + Access::DeviceToHost, + DmaOptions::empty(), + ); + match DMA_POOL_DEVICE_TO_HOST.set(pool) { + Ok(_) => {} + Err(_) => panic!("Failed to set DMA_POOL_DEVICE_TO_HOST"), + }; + match DMA_POOL_DEVICE_TO_HOST.get() { + Some(pool) => pool, + None => panic!("Failed to set DMA_POOL_DEVICE_TO_HOST"), + } + } + }, + BufferDirection::Both => match DMA_POOL_BIDIRECTIONAL.get() { + Some(pool) => pool, + None => { + let pool = DmaPool::new( + DmaPool::default_spec(), + Access::BiDirectional, + DmaOptions::empty(), + ); + match DMA_POOL_BIDIRECTIONAL.set(pool) { + Ok(_) => {} + Err(_) => panic!("Failed to set DMA_POOL_BIDIRECTIONAL"), + }; + match DMA_POOL_BIDIRECTIONAL.get() { + Some(pool) => pool, + None => panic!("Failed to set DMA_POOL_BIDIRECTIONAL"), + } + } + }, + } +} + +fn insert_alloced(paddr: PhysAddr, dma_slice: DmaSliceRegion) { + let dict = ALLOCED.get_or_init(|| Mutex::new(HashMap::new())); + let wrapped = Fragile::new(dma_slice); + dict.lock().unwrap().insert(paddr, wrapped); +} + +fn remove_alloced(paddr: PhysAddr) -> Option>> { + let dict = ALLOCED.get_or_init(|| Mutex::new(HashMap::new())); + dict.lock().unwrap().remove(&paddr) +} + +unsafe impl Hal for TestHal { + // Required methods + fn dma_alloc(pages: usize, direction: BufferDirection) -> (PhysAddr, NonNull) { + assert!(pages == 1, "Only 1 page supported"); + + let pool = get_dma_pool(direction); + let alloced = pool.allocate_array(pages, 0u8).unwrap(); + let mut dma_slice = alloced; + + let pin = dma_slice.pin().unwrap(); + let phys_addr: virtio_drivers::PhysAddr = + u64::from(pin.into_iter().next().unwrap().addr()) as virtio_drivers::PhysAddr; + let virt = unsafe { NonNull::::new(dma_slice.get_mut().as_mut_ptr()) }.unwrap(); + + // Persist the allocated memory so it isn't freed when the function returns + insert_alloced(phys_addr, dma_slice); + (phys_addr as PhysAddr, virt) + } + + unsafe fn dma_dealloc(_paddr: PhysAddr, _vaddr: NonNull, _pages: usize) -> i32 { + let dma_region = remove_alloced(_paddr).unwrap().try_into_inner(); + match dma_region { + Ok(mut dma_region) => { + dma_region.release_pin(); + 0 + } + Err(_) => 1, + } + } + + unsafe fn mmio_phys_to_virt(_paddr: PhysAddr, _size: usize) -> NonNull { + panic!("Should never be called as we have our own transport implementation"); + } + + unsafe fn share(buffer: NonNull<[u8]>, direction: BufferDirection) -> PhysAddr { + let buf_len = buffer.len(); + assert!(buf_len <= DMA_PAGE_SIZE, "Hal::Share(): Buffer too large"); + let (phys, virt) = TestHal::dma_alloc(1, direction); + let slice = remove_alloced(phys).unwrap().into_inner(); + + let buf_casted = buffer.cast::(); + let buf = buf_casted.as_ptr(); + let dma_buf = virt.as_ptr(); + // Copy the buffer to the DMA buffer + copy_nonoverlapping(buf, dma_buf, buf_len); + + match direction { + BufferDirection::DriverToDevice => { + slice.sync(0..buf_len, SyncMode::PostCpuToDevice); + } + BufferDirection::DeviceToDriver => { + slice.sync(0..buf_len, SyncMode::PreDeviceToCpu); + } + _ => {} + } + // Persist the allocated memory so it isn't freed when the function returns + insert_alloced(phys, slice); + phys as PhysAddr + } + unsafe fn unshare(paddr: PhysAddr, buffer: NonNull<[u8]>, direction: BufferDirection) { + // Gets DMA buffer and unallocates it + let mut dma_slice = remove_alloced(paddr).unwrap().into_inner(); + match direction { + BufferDirection::DeviceToDriver => { + dma_slice.sync(0..buffer.len(), SyncMode::PostDeviceToCpu); + } + _ => {} + } + + let buf_len = buffer.len(); + let buf_casted = buffer.cast::(); + let buf = buf_casted.as_ptr(); + let dma_buf = unsafe { dma_slice.get_mut().as_ptr() }; + + // Copy the DMA buffer back to the buffer + copy_nonoverlapping(dma_buf, buf, buf_len); + dma_slice.release_pin(); + } +} diff --git a/src/lib/virtio-net/src/lib.rs b/src/lib/virtio-net/src/lib.rs new file mode 100644 index 00000000..6c5195a8 --- /dev/null +++ b/src/lib/virtio-net/src/lib.rs @@ -0,0 +1,8 @@ +//! Virtio network device driver. +//! +//! Provides smoltcp types for use with the virtio network device. +mod hal; +mod tcp; +mod transport; + +pub use tcp::{get_device, DeviceWrapper, VirtioRxToken, VirtioTxToken}; diff --git a/src/lib/virtio-net/src/tcp.rs b/src/lib/virtio-net/src/tcp.rs new file mode 100644 index 00000000..d04f07ef --- /dev/null +++ b/src/lib/virtio-net/src/tcp.rs @@ -0,0 +1,209 @@ +//! Simple echo server over TCP. +//! +//! Ref: +use core::{cell::RefCell, str::FromStr}; +use std::{borrow::ToOwned, rc::Rc, vec, vec::Vec}; + +use smoltcp::{ + iface::{Config, Interface, SocketSet}, + phy::{Device, DeviceCapabilities, Medium, RxToken, TxToken}, + socket::tcp, + time::Instant, + wire::{EthernetAddress, HardwareAddress, IpAddress, IpCidr, Ipv4Address}, +}; +use virtio_drivers::{ + device::net::{RxBuffer, VirtIONet}, + transport::Transport, + Error, +}; + +use crate::{hal::TestHal, transport::TwizzlerTransport}; + +const NET_QUEUE_SIZE: usize = 16; + +type DeviceImpl = VirtIONet; + +const NET_BUFFER_LEN: usize = 2048; + +pub struct DeviceWrapper { + inner: Rc>>, +} + +impl DeviceWrapper { + fn new(dev: DeviceImpl) -> Self { + DeviceWrapper { + inner: Rc::new(RefCell::new(dev)), + } + } + + pub fn mac_address(&self) -> EthernetAddress { + EthernetAddress(self.inner.borrow().mac_address()) + } +} + +impl Device for DeviceWrapper { + type RxToken<'a> + = VirtioRxToken + where + Self: 'a; + type TxToken<'a> + = VirtioTxToken + where + Self: 'a; + + fn receive(&mut self, _timestamp: Instant) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)> { + match self.inner.borrow_mut().receive() { + Ok(buf) => Some(( + VirtioRxToken(self.inner.clone(), buf), + VirtioTxToken(self.inner.clone()), + )), + Err(Error::NotReady) => None, + Err(err) => panic!("receive failed: {}", err), + } + } + + fn transmit(&mut self, _timestamp: Instant) -> Option> { + Some(VirtioTxToken(self.inner.clone())) + } + + fn capabilities(&self) -> DeviceCapabilities { + let mut caps = DeviceCapabilities::default(); + caps.max_transmission_unit = 1536; + caps.max_burst_size = Some(1); + caps.medium = Medium::Ethernet; + caps + } +} + +pub struct VirtioRxToken(Rc>>, RxBuffer); +pub struct VirtioTxToken(Rc>>); + +impl RxToken for VirtioRxToken { + fn consume(self, f: F) -> R + where + F: FnOnce(&mut [u8]) -> R, + { + let mut rx_buf = self.1; + // println!( + // "RECV {} bytes: {:02X?}", + // rx_buf.packet_len(), + // rx_buf.packet() + // ); + // println!("RX BUFFER ADDR: {:p}", rx_buf.packet_mut()); + let result = f(rx_buf.packet_mut()); + self.0.borrow_mut().recycle_rx_buffer(rx_buf).unwrap(); + result + } +} + +impl TxToken for VirtioTxToken { + fn consume(self, len: usize, f: F) -> R + where + F: FnOnce(&mut [u8]) -> R, + { + let mut dev = self.0.borrow_mut(); + let mut tx_buf = dev.new_tx_buffer(len); + let result = f(tx_buf.packet_mut()); + // println!("SEND {} bytes: {:02X?}", len, tx_buf.packet()); + // println!("TX BUFFER ADDR: {:p}", tx_buf.packet_mut()); + dev.send(tx_buf).unwrap(); + result + } +} + +// Gets the Virtio Net struct which implements the device used for smoltcp. Use this to create a +// smoltcp interface to send and receive packets. NOTE: Only the first device used will work +// properly +pub fn get_device() -> DeviceWrapper { + let net = VirtIONet::::new( + TwizzlerTransport::new().unwrap(), + NET_BUFFER_LEN, + ) + .expect("failed to create net driver"); + DeviceWrapper::::new(net) +} + +#[allow(dead_code)] +fn test_echo_server() { + const IP: &str = "10.0.2.15"; // QEMU user networking default IP + const GATEWAY: &str = "10.0.2.2"; // QEMU user networking gateway + const PORT: u16 = 5555; + let mut device = get_device(); + + if device.capabilities().medium != Medium::Ethernet { + panic!("This implementation only supports virtio-net which is an ethernet device"); + } + + let hardware_addr = HardwareAddress::Ethernet(device.mac_address()); + + // Create interface + let mut config = Config::new(hardware_addr); + config.random_seed = 0x2333; + + let mut iface = Interface::new(config, &mut device, Instant::now()); + iface.update_ip_addrs(|ip_addrs| { + ip_addrs + .push(IpCidr::new(IpAddress::from_str(IP).unwrap(), 24)) + .unwrap(); + }); + + iface + .routes_mut() + .add_default_ipv4_route(Ipv4Address::from_str(GATEWAY).unwrap()) + .unwrap(); + + // Create sockets + let tcp_rx_buffer = tcp::SocketBuffer::new(vec![0; 1024]); + let tcp_tx_buffer = tcp::SocketBuffer::new(vec![0; 1024]); + let tcp_socket = tcp::Socket::new(tcp_rx_buffer, tcp_tx_buffer); + + let mut sockets = SocketSet::new(vec![]); + let tcp_handle = sockets.add(tcp_socket); + + println!("start a echo server..."); + let mut tcp_active = false; + loop { + let timestamp = Instant::now(); + + iface.poll(timestamp, &mut device, &mut sockets); + + let socket = sockets.get_mut::(tcp_handle); + if !socket.is_open() { + println!("listening on port {}...", PORT); + socket.listen(PORT).unwrap(); + } + + if socket.is_active() && !tcp_active { + println!("tcp:{} connected", PORT); + } else if !socket.is_active() && tcp_active { + println!("tcp:{} disconnected", PORT); + } + tcp_active = socket.is_active(); + + if socket.may_recv() { + let data = socket + .recv(|buffer| { + let recvd_len = buffer.len(); + if !buffer.is_empty() { + println!("tcp:{} recv {} bytes: {:?}", PORT, recvd_len, buffer); + let lines = buffer + .split(|&b| b == b'\n') + .map(ToOwned::to_owned) + .collect::>(); + let data = lines.join(&b'\n'); + (recvd_len, data) + } else { + (0, vec![]) + } + }) + .unwrap(); + if socket.can_send() && !data.is_empty() { + println!("tcp:{} send data: {:?}", PORT, data); + socket.send_slice(&data[..]).unwrap(); + } + } else if socket.may_send() { + println!("tcp:{} close", PORT); + socket.close(); + } + } +} diff --git a/src/lib/virtio-net/src/transport.rs b/src/lib/virtio-net/src/transport.rs new file mode 100644 index 00000000..5a8601fa --- /dev/null +++ b/src/lib/virtio-net/src/transport.rs @@ -0,0 +1,368 @@ +use core::{ + mem::{align_of, size_of}, + ptr::NonNull, +}; + +use twizzler_abi::device::bus::pcie::PcieDeviceInfo; +use twizzler_driver::{bus::pcie::PcieCapability, device::Device}; +use virtio_drivers::{ + transport::{pci::VirtioPciError, DeviceStatus, DeviceType, Transport}, + Error, +}; +use virtio_pcie::{VirtioIsrStatus, VirtioPciNotifyCap}; +use volatile::{map_field, VolatilePtr}; + +pub mod virtio_pcie; +use self::virtio_pcie::{CfgLocation, VirtioCfgType, VirtioCommonCfg, VirtioPciCap}; + +pub struct TwizzlerTransport { + device: Device, + + common_cfg: CfgLocation, + + notify_region: CfgLocation, + notify_offset_multiplier: u32, + + isr_status: CfgLocation, + + config_space: Option>, +} + +fn get_device() -> Device { + let device_root = twizzler_driver::get_bustree_root(); + for device in device_root.children() { + if device.is_bus() && device.bus_type() == twizzler_abi::device::BusType::Pcie { + for child in device.children() { + let info = unsafe { child.get_info::(0).unwrap() }; + // Can be modified later to let us select any other virtio device we want. For now, + // just network is good. + if info.get_data().class == 2 + && info.get_data().subclass == 0 + && info.get_data().progif == 0 + && info.get_data().vendor_id == 0x1AF4 + { + println!("Found VirtIO networking device!"); + + return child; + } + } + } + } + panic!("No VirtIO networking device found"); +} + +impl TwizzlerTransport { + pub fn new() -> Result { + let device = get_device(); + let info = unsafe { device.get_info::(0).unwrap() }; + if info.get_data().vendor_id != 0x1AF4 { + println!("Vendor ID: {}", info.get_data().vendor_id); + return Err(VirtioPciError::InvalidVendorId(info.get_data().vendor_id)); + } + + let mut common_cfg = None; + let mut notify_region = None; + let mut notify_offset_multiplier = 0; + let mut isr_status = None; + let mut config_space = None; + + let mm = device.find_mmio_bar(0xff).unwrap(); + for cap in device.pcie_capabilities(&mm).unwrap() { + let off: usize = match cap { + PcieCapability::VendorSpecific(x) => x, + _ => { + continue; + } + }; + + let mut virtio_cfg_ref = unsafe { mm.get_mmio_offset_mut::(off) }; + let virtio_cfg = virtio_cfg_ref.as_mut_ptr(); + match map_field!(virtio_cfg.cfg_type).read() { + VirtioCfgType::CommonCfg if common_cfg.is_none() => { + println!( + "Common CFG found! Bar: {:?}, Offset: {:?}, Length: {:?}", + map_field!(virtio_cfg.bar).read(), + map_field!(virtio_cfg.offset).read(), + map_field!(virtio_cfg.length).read() + ); + common_cfg = Some(CfgLocation { + bar: map_field!(virtio_cfg.bar).read() as usize, + offset: map_field!(virtio_cfg.offset).read() as usize, + length: map_field!(virtio_cfg.length).read() as usize, + }); + } + VirtioCfgType::NotifyCfg if notify_region.is_none() => { + let mut notify_ref = + unsafe { mm.get_mmio_offset_mut::(off) }; + let notify_cap = notify_ref.as_mut_ptr(); + notify_offset_multiplier = map_field!(notify_cap.notify_off_multiplier).read(); + println!("Notify CFG found! Bar: {:?}, Offset: {:?}, Length: {:?}, Offset multiplier: {:?}", map_field!(virtio_cfg.bar).read(), map_field!(virtio_cfg.offset).read(), map_field!(virtio_cfg.length).read(), notify_offset_multiplier); + notify_region = Some(CfgLocation { + bar: map_field!(virtio_cfg.bar).read() as usize, + offset: map_field!(virtio_cfg.offset).read() as usize, + length: map_field!(virtio_cfg.length).read() as usize, + }) + } + + VirtioCfgType::IsrCfg if isr_status.is_none() => { + println!( + "ISR CFG found! Bar: {:?}, Offset: {:?}, Length: {:?}", + map_field!(virtio_cfg.bar).read(), + map_field!(virtio_cfg.offset).read(), + map_field!(virtio_cfg.length).read() + ); + isr_status = Some(CfgLocation { + bar: map_field!(virtio_cfg.bar).read() as usize, + offset: map_field!(virtio_cfg.offset).read() as usize, + length: map_field!(virtio_cfg.length).read() as usize, + }); + } + + VirtioCfgType::DeviceCfg if config_space.is_none() => { + println!( + "Device CFG found! Bar: {:?}, Offset: {:?}, Length: {:?}", + map_field!(virtio_cfg.bar).read(), + map_field!(virtio_cfg.offset).read(), + map_field!(virtio_cfg.length).read() + ); + let bar_num = map_field!(virtio_cfg.bar).read() as usize; + let bar = device.find_mmio_bar(bar_num).unwrap(); + let mut start = unsafe { + bar.get_mmio_offset_mut::(map_field!(virtio_cfg.offset).read() as usize) + }; + let len = map_field!(virtio_cfg.length).read() as usize; + + let ptr = unsafe { + NonNull::from(core::slice::from_raw_parts_mut( + start.as_mut_ptr().as_raw_ptr().as_ptr(), + len, + )) + }; + config_space = Some(ptr); + } + _ => {} + } + } + let common_cfg = common_cfg.ok_or(VirtioPciError::MissingCommonConfig)?; + let notify_region = notify_region.ok_or(VirtioPciError::MissingNotifyConfig)?; + let isr_status = isr_status.ok_or(VirtioPciError::MissingIsrConfig)?; + + Ok(Self { + device, + common_cfg, + notify_region, + notify_offset_multiplier, + isr_status, + config_space, + }) + } +} + +impl Transport for TwizzlerTransport { + fn device_type(&self) -> DeviceType { + device_type( + unsafe { self.device.get_info::(0) } + .unwrap() + .get_data() + .device_id, + ) + } + + fn read_device_features(&mut self) -> u64 { + let bar = self.device.find_mmio_bar(self.common_cfg.bar).unwrap(); + let mut reference = + unsafe { bar.get_mmio_offset_mut::(self.common_cfg.offset) }; + let ptr = reference.as_mut_ptr(); + + map_field!(ptr.device_feature_select).write(0); + let mut device_feature_bits = map_field!(ptr.device_feature).read() as u64; + map_field!(ptr.device_feature_select).write(1); + device_feature_bits |= (map_field!(ptr.device_feature).read() as u64) << 32; + device_feature_bits + } + + fn write_driver_features(&mut self, driver_features: u64) { + let bar = self.device.find_mmio_bar(self.common_cfg.bar).unwrap(); + let mut reference = + unsafe { bar.get_mmio_offset_mut::(self.common_cfg.offset) }; + let ptr = reference.as_mut_ptr(); + + map_field!(ptr.driver_feature_select).write(0); + map_field!(ptr.driver_feature).write(driver_features as u32); + map_field!(ptr.driver_feature_select).write(1); + map_field!(ptr.driver_feature).write((driver_features >> 32) as u32); + } + + fn max_queue_size(&mut self, queue: u16) -> u32 { + let bar = self.device.find_mmio_bar(self.common_cfg.bar).unwrap(); + let mut reference = + unsafe { bar.get_mmio_offset_mut::(self.common_cfg.offset) }; + let ptr = reference.as_mut_ptr(); + + map_field!(ptr.queue_select).write(queue); + map_field!(ptr.queue_size).read().into() + } + + fn notify(&mut self, queue: u16) { + let bar = self.device.find_mmio_bar(self.common_cfg.bar).unwrap(); + let mut reference = + unsafe { bar.get_mmio_offset_mut::(self.common_cfg.offset) }; + let ptr = reference.as_mut_ptr(); + + map_field!(ptr.queue_select).write(queue); + + let queue_notify_off = map_field!(ptr.queue_notify_off).read(); + + let offset_bytes = queue_notify_off as usize * self.notify_offset_multiplier as usize; + let index = offset_bytes / size_of::(); + + let notify_bar = self.device.find_mmio_bar(self.notify_region.bar).unwrap(); + let start = unsafe { + notify_bar + .get_mmio_offset_mut::(self.notify_region.offset as usize) + .as_mut_ptr() + .as_raw_ptr() + .as_ptr() + }; + + let notify_ptr = unsafe { + VolatilePtr::new(NonNull::from(core::slice::from_raw_parts_mut( + start, + self.notify_region.length as usize, + ))) + }; + + let to_write = notify_ptr.index(index); + to_write.write(queue); + } + + fn get_status(&self) -> virtio_drivers::transport::DeviceStatus { + let bar = self.device.find_mmio_bar(self.common_cfg.bar).unwrap(); + let mut reference = + unsafe { bar.get_mmio_offset_mut::(self.common_cfg.offset) }; + let ptr = reference.as_mut_ptr(); + + let status = map_field!(ptr.device_status).read(); + DeviceStatus::from_bits_truncate(status.into()) + } + + fn set_status(&mut self, status: virtio_drivers::transport::DeviceStatus) { + let bar = self.device.find_mmio_bar(self.common_cfg.bar).unwrap(); + let mut reference = + unsafe { bar.get_mmio_offset_mut::(self.common_cfg.offset) }; + let ptr = reference.as_mut_ptr(); + + map_field!(ptr.device_status).write(status.bits() as u8); + } + + fn set_guest_page_size(&mut self, _guest_page_size: u32) { + // No-op, the PCI transport doesn't care. + } + + fn requires_legacy_layout(&self) -> bool { + false + } + + fn queue_set( + &mut self, + queue: u16, + size: u32, + descriptors: virtio_drivers::PhysAddr, + driver_area: virtio_drivers::PhysAddr, + device_area: virtio_drivers::PhysAddr, + ) { + let bar = self.device.find_mmio_bar(self.common_cfg.bar).unwrap(); + let mut reference = + unsafe { bar.get_mmio_offset_mut::(self.common_cfg.offset) }; + let ptr = reference.as_mut_ptr(); + + map_field!(ptr.queue_select).write(queue); + map_field!(ptr.queue_size).write(size as u16); + map_field!(ptr.queue_desc).write(descriptors.try_into().unwrap()); + map_field!(ptr.queue_driver).write(driver_area.try_into().unwrap()); + map_field!(ptr.queue_device).write(device_area.try_into().unwrap()); + map_field!(ptr.queue_enable).write(1); + } + + fn queue_unset(&mut self, _queue: u16) { + // The VirtIO spec doesn't allow queues to be unset once they have been set up for the PCI + // transport, so this is a no-op. + } + + fn queue_used(&mut self, queue: u16) -> bool { + let bar = self.device.find_mmio_bar(self.common_cfg.bar).unwrap(); + let mut reference = + unsafe { bar.get_mmio_offset_mut::(self.common_cfg.offset) }; + let ptr = reference.as_mut_ptr(); + + map_field!(ptr.queue_select).write(queue); + map_field!(ptr.queue_enable).read() == 1 + } + + fn ack_interrupt(&mut self) -> bool { + let bar = self.device.find_mmio_bar(self.isr_status.bar).unwrap(); + let mut reference = + unsafe { bar.get_mmio_offset_mut::(self.isr_status.offset) }; + let ptr = reference.as_mut_ptr(); + + let status = ptr.read(); + status & 0x3 != 0 + } + + //Taken from the provided virtio drivers pci transport + fn config_space(&self) -> virtio_drivers::Result> { + if let Some(config_space) = self.config_space { + if size_of::() > config_space.len() * size_of::() { + Err(Error::ConfigSpaceTooSmall) + } else if align_of::() > 4 { + // Panic as this should only happen if the driver is written incorrectly. + panic!( + "Driver expected config space alignment of {} bytes, but VirtIO only guarantees 4 byte alignment.", + align_of::() + ); + } else { + // TODO: Use NonNull::as_non_null_ptr once it is stable. + let config_space_ptr = NonNull::new(config_space.as_ptr() as *mut u32).unwrap(); + Ok(config_space_ptr.cast()) + } + } else { + Err(Error::ConfigSpaceMissing) + } + } +} + +impl Drop for TwizzlerTransport { + fn drop(&mut self) { + // Disable the device + self.set_status(DeviceStatus::empty()); + while self.get_status() != DeviceStatus::empty() { + // Wait for the device to acknowledge the status change + core::hint::spin_loop(); + } + } +} + +/// The offset to add to a VirtIO device ID to get the corresponding PCI device ID. +const PCI_DEVICE_ID_OFFSET: u16 = 0x1040; + +const TRANSITIONAL_NETWORK: u16 = 0x1000; +const TRANSITIONAL_BLOCK: u16 = 0x1001; +const TRANSITIONAL_MEMORY_BALLOONING: u16 = 0x1002; +const TRANSITIONAL_CONSOLE: u16 = 0x1003; +const TRANSITIONAL_SCSI_HOST: u16 = 0x1004; +const TRANSITIONAL_ENTROPY_SOURCE: u16 = 0x1005; +const TRANSITIONAL_9P_TRANSPORT: u16 = 0x1009; + +fn device_type(pci_device_id: u16) -> DeviceType { + match pci_device_id { + TRANSITIONAL_NETWORK => DeviceType::Network, + TRANSITIONAL_BLOCK => DeviceType::Block, + TRANSITIONAL_MEMORY_BALLOONING => DeviceType::MemoryBalloon, + TRANSITIONAL_CONSOLE => DeviceType::Console, + TRANSITIONAL_SCSI_HOST => DeviceType::ScsiHost, + TRANSITIONAL_ENTROPY_SOURCE => DeviceType::EntropySource, + TRANSITIONAL_9P_TRANSPORT => DeviceType::_9P, + id if id >= PCI_DEVICE_ID_OFFSET => DeviceType::from(id - PCI_DEVICE_ID_OFFSET), + _ => DeviceType::Invalid, + } +} diff --git a/src/lib/virtio-net/src/transport/virtio_pcie.rs b/src/lib/virtio-net/src/transport/virtio_pcie.rs new file mode 100644 index 00000000..24415fbe --- /dev/null +++ b/src/lib/virtio-net/src/transport/virtio_pcie.rs @@ -0,0 +1,61 @@ +// Virtio vendor specific PCI Capability +#[repr(C)] +pub struct VirtioPciCap { + pub cap_vndr: u8, /* Generic PCI field: PCI_CAP_ID_VNDR */ + pub cap_next: u8, /* Generic PCI field: next ptr. */ + pub cap_len: u8, /* Generic PCI field: capability length */ + pub cfg_type: VirtioCfgType, /* Identifies the structure. */ + pub bar: u8, /* Where to find it. */ + pub id: u8, /* Multiple capabilities of the same type */ + pub padding: [u8; 2], /* Pad to full dword. */ + pub offset: u32, /* Offset within bar. */ + pub length: u32, /* Length of the structure, in bytes. */ +} + +#[derive(Copy, Clone)] +#[allow(dead_code)] +pub enum VirtioCfgType { + CommonCfg = 1, + NotifyCfg = 2, + IsrCfg = 3, + DeviceCfg = 4, + PciCfg = 5, + SharedMemoryCfg = 8, + VendorCfg = 9, +} +#[repr(C)] +pub struct VirtioPciNotifyCap { + pub virtio_pci_cap: VirtioPciCap, + pub notify_off_multiplier: u32, /* Multiplier for queue_notify_off. */ +} + +#[repr(C)] +pub struct VirtioCommonCfg { + pub device_feature_select: u32, + pub device_feature: u32, + pub driver_feature_select: u32, + pub driver_feature: u32, + pub config_msix_vector: u16, + pub num_queues: u16, + pub device_status: u8, + pub config_generation: u8, + + pub queue_select: u16, + pub queue_size: u16, + pub queue_msix_vector: u16, + pub queue_enable: u16, + pub queue_notify_off: u16, + pub queue_desc: u64, + pub queue_driver: u64, + pub queue_device: u64, + pub queue_notify_data: u16, + pub queue_reset: u16, +} + +pub type VirtioIsrStatus = u8; + +pub struct CfgLocation { + pub bar: usize, + pub offset: usize, + pub length: usize, +} diff --git a/tools/xtask/src/qemu.rs b/tools/xtask/src/qemu.rs index 22284aa0..dadfae85 100644 --- a/tools/xtask/src/qemu.rs +++ b/tools/xtask/src/qemu.rs @@ -66,6 +66,9 @@ impl QemuCommand { .arg("-device") .arg("nvme,serial=deadbeef,drive=nvme"); + self.cmd.arg("-device").arg("virtio-net-pci,netdev=net0"); + self.cmd.arg("-netdev").arg("user,id=net0,hostfwd=tcp::5555-:5555"); + self.cmd .arg("--no-reboot") // exit instead of rebooting //.arg("-s") // shorthand for -gdb tcp::1234