You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
295 lines
8.7 KiB
295 lines
8.7 KiB
use alloc::alloc::{GlobalAlloc, Layout};
|
|
use alloc::format;
|
|
use alloc::string::String;
|
|
use alloc::sync::Arc;
|
|
use core::mem::size_of;
|
|
use core::slice;
|
|
|
|
use bitflags::*;
|
|
use device_tree::util::SliceRead;
|
|
use device_tree::Node;
|
|
use log::*;
|
|
use rcore_memory::PAGE_SIZE;
|
|
use smoltcp::phy::{self, DeviceCapabilities};
|
|
use smoltcp::time::Instant;
|
|
use smoltcp::wire::{EthernetAddress, Ipv4Address};
|
|
use smoltcp::Result;
|
|
use volatile::{ReadOnly, Volatile};
|
|
|
|
use crate::sync::SpinNoIrqLock as Mutex;
|
|
use crate::HEAP_ALLOCATOR;
|
|
|
|
use super::super::bus::virtio_mmio::*;
|
|
use super::super::{DeviceType, Driver, DRIVERS, NET_DRIVERS};
|
|
use crate::memory::phys_to_virt;
|
|
|
|
pub struct VirtIONet {
|
|
interrupt_parent: u32,
|
|
interrupt: u32,
|
|
header: usize,
|
|
mac: EthernetAddress,
|
|
// 0 for receive, 1 for transmit
|
|
queues: [VirtIOVirtqueue; 2],
|
|
}
|
|
|
|
#[derive(Clone)]
|
|
pub struct VirtIONetDriver(Arc<Mutex<VirtIONet>>);
|
|
|
|
const VIRTIO_QUEUE_RECEIVE: usize = 0;
|
|
const VIRTIO_QUEUE_TRANSMIT: usize = 1;
|
|
|
|
impl Driver for VirtIONetDriver {
|
|
fn try_handle_interrupt(&self, _irq: Option<u32>) -> bool {
|
|
let driver = self.0.lock();
|
|
|
|
let header = unsafe { &mut *(driver.header as *mut VirtIOHeader) };
|
|
let interrupt = header.interrupt_status.read();
|
|
if interrupt != 0 {
|
|
header.interrupt_ack.write(interrupt);
|
|
let interrupt_status = VirtIONetworkInterruptStatus::from_bits_truncate(interrupt);
|
|
debug!("Got interrupt {:?}", interrupt_status);
|
|
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
fn device_type(&self) -> DeviceType {
|
|
DeviceType::Net
|
|
}
|
|
|
|
fn get_id(&self) -> String {
|
|
format!("virtio_net")
|
|
}
|
|
|
|
fn get_mac(&self) -> EthernetAddress {
|
|
self.0.lock().mac
|
|
}
|
|
|
|
fn get_ifname(&self) -> String {
|
|
format!("virtio{}", self.0.lock().interrupt)
|
|
}
|
|
|
|
fn ipv4_address(&self) -> Option<Ipv4Address> {
|
|
unimplemented!()
|
|
}
|
|
|
|
fn poll(&self) {
|
|
unimplemented!()
|
|
}
|
|
}
|
|
|
|
impl VirtIONet {
|
|
fn transmit_available(&self) -> bool {
|
|
self.queues[VIRTIO_QUEUE_TRANSMIT].can_add(1, 0)
|
|
}
|
|
|
|
fn receive_available(&self) -> bool {
|
|
self.queues[VIRTIO_QUEUE_RECEIVE].can_get()
|
|
}
|
|
}
|
|
|
|
pub struct VirtIONetRxToken(VirtIONetDriver);
|
|
pub struct VirtIONetTxToken(VirtIONetDriver);
|
|
|
|
impl<'a> phy::Device<'a> for VirtIONetDriver {
|
|
type RxToken = VirtIONetRxToken;
|
|
type TxToken = VirtIONetTxToken;
|
|
|
|
fn receive(&'a mut self) -> Option<(Self::RxToken, Self::TxToken)> {
|
|
let driver = self.0.lock();
|
|
if driver.transmit_available() && driver.receive_available() {
|
|
// potential racing
|
|
Some((
|
|
VirtIONetRxToken(self.clone()),
|
|
VirtIONetTxToken(self.clone()),
|
|
))
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
|
|
fn transmit(&'a mut self) -> Option<Self::TxToken> {
|
|
let driver = self.0.lock();
|
|
if driver.transmit_available() {
|
|
Some(VirtIONetTxToken(self.clone()))
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
|
|
fn capabilities(&self) -> DeviceCapabilities {
|
|
let mut caps = DeviceCapabilities::default();
|
|
caps.max_transmission_unit = 1536;
|
|
caps.max_burst_size = Some(1);
|
|
caps
|
|
}
|
|
}
|
|
|
|
impl phy::RxToken for VirtIONetRxToken {
|
|
fn consume<R, F>(self, _timestamp: Instant, f: F) -> Result<R>
|
|
where
|
|
F: FnOnce(&[u8]) -> Result<R>,
|
|
{
|
|
let (input, output, _, user_data) = {
|
|
let mut driver = (self.0).0.lock();
|
|
driver.queues[VIRTIO_QUEUE_RECEIVE].get().unwrap()
|
|
};
|
|
let result = f(&input[0][size_of::<VirtIONetHeader>()..]);
|
|
|
|
let mut driver = (self.0).0.lock();
|
|
driver.queues[VIRTIO_QUEUE_RECEIVE].add_and_notify(&input, &output, user_data);
|
|
result
|
|
}
|
|
}
|
|
|
|
impl phy::TxToken for VirtIONetTxToken {
|
|
fn consume<R, F>(self, _timestamp: Instant, len: usize, f: F) -> Result<R>
|
|
where
|
|
F: FnOnce(&mut [u8]) -> Result<R>,
|
|
{
|
|
let output = {
|
|
let mut driver = (self.0).0.lock();
|
|
if let Some((_, output, _, _)) = driver.queues[VIRTIO_QUEUE_TRANSMIT].get() {
|
|
unsafe { slice::from_raw_parts_mut(output[0].as_ptr() as *mut u8, output[0].len()) }
|
|
} else {
|
|
// allocate a page for buffer
|
|
let page = unsafe {
|
|
HEAP_ALLOCATOR
|
|
.alloc_zeroed(Layout::from_size_align(PAGE_SIZE, PAGE_SIZE).unwrap())
|
|
} as usize;
|
|
unsafe { slice::from_raw_parts_mut(page as *mut u8, PAGE_SIZE) }
|
|
}
|
|
};
|
|
let output_buffer =
|
|
&mut output[size_of::<VirtIONetHeader>()..(size_of::<VirtIONetHeader>() + len)];
|
|
let result = f(output_buffer);
|
|
|
|
let mut driver = (self.0).0.lock();
|
|
assert!(driver.queues[VIRTIO_QUEUE_TRANSMIT].add_and_notify(&[], &[output], 0));
|
|
result
|
|
}
|
|
}
|
|
|
|
bitflags! {
|
|
struct VirtIONetFeature : u64 {
|
|
const CSUM = 1 << 0;
|
|
const GUEST_CSUM = 1 << 1;
|
|
const CTRL_GUEST_OFFLOADS = 1 << 2;
|
|
const MTU = 1 << 3;
|
|
const MAC = 1 << 5;
|
|
const GSO = 1 << 6;
|
|
const GUEST_TSO4 = 1 << 7;
|
|
const GUEST_TSO6 = 1 << 8;
|
|
const GUEST_ECN = 1 << 9;
|
|
const GUEST_UFO = 1 << 10;
|
|
const HOST_TSO4 = 1 << 11;
|
|
const HOST_TSO6 = 1 << 12;
|
|
const HOST_ECN = 1 << 13;
|
|
const HOST_UFO = 1 << 14;
|
|
const MRG_RXBUF = 1 << 15;
|
|
const STATUS = 1 << 16;
|
|
const CTRL_VQ = 1 << 17;
|
|
const CTRL_RX = 1 << 18;
|
|
const CTRL_VLAN = 1 << 19;
|
|
const CTRL_RX_EXTRA = 1 << 20;
|
|
const GUEST_ANNOUNCE = 1 << 21;
|
|
const MQ = 1 << 22;
|
|
const CTL_MAC_ADDR = 1 << 23;
|
|
// device independent
|
|
const RING_INDIRECT_DESC = 1 << 28;
|
|
const RING_EVENT_IDX = 1 << 29;
|
|
const VERSION_1 = 1 << 32; // legacy
|
|
}
|
|
}
|
|
|
|
bitflags! {
|
|
struct VirtIONetworkStatus : u16 {
|
|
const LINK_UP = 1;
|
|
const ANNOUNCE = 2;
|
|
}
|
|
}
|
|
|
|
bitflags! {
|
|
struct VirtIONetworkInterruptStatus : u32 {
|
|
const USED_RING_UPDATE = 1 << 0;
|
|
const CONFIGURATION_CHANGE = 1 << 1;
|
|
}
|
|
}
|
|
|
|
#[repr(C)]
|
|
#[derive(Debug)]
|
|
struct VirtIONetworkConfig {
|
|
mac: [u8; 6],
|
|
status: ReadOnly<u16>,
|
|
}
|
|
|
|
// virtio 5.1.6 Device Operation
|
|
#[repr(C)]
|
|
#[derive(Debug)]
|
|
struct VirtIONetHeader {
|
|
flags: Volatile<u8>,
|
|
gso_type: Volatile<u8>,
|
|
hdr_len: Volatile<u16>, // cannot rely on this
|
|
gso_size: Volatile<u16>,
|
|
csum_start: Volatile<u16>,
|
|
csum_offset: Volatile<u16>,
|
|
// payload starts from here
|
|
}
|
|
|
|
pub fn virtio_net_init(node: &Node) {
|
|
let reg = node.prop_raw("reg").unwrap();
|
|
let paddr = reg.as_slice().read_be_u64(0).unwrap();
|
|
let vaddr = phys_to_virt(paddr as usize);
|
|
let header = unsafe { &mut *(vaddr as *mut VirtIOHeader) };
|
|
|
|
header.status.write(VirtIODeviceStatus::DRIVER.bits());
|
|
|
|
let device_features_bits = header.read_device_features();
|
|
let device_features = VirtIONetFeature::from_bits_truncate(device_features_bits);
|
|
debug!("Device features {:?}", device_features);
|
|
|
|
// negotiate these flags only
|
|
let supported_features = VirtIONetFeature::MAC | VirtIONetFeature::STATUS;
|
|
let driver_features = (device_features & supported_features).bits();
|
|
header.write_driver_features(driver_features);
|
|
|
|
// read configuration space
|
|
let config =
|
|
unsafe { &mut *((vaddr + VIRTIO_CONFIG_SPACE_OFFSET) as *mut VirtIONetworkConfig) };
|
|
let mac = config.mac;
|
|
let status = VirtIONetworkStatus::from_bits_truncate(config.status.read());
|
|
debug!("Got MAC address {:?} and status {:?}", mac, status);
|
|
|
|
// virtio 4.2.4 Legacy interface
|
|
// configure two virtqueues: ingress and egress
|
|
header.guest_page_size.write(PAGE_SIZE as u32); // one page
|
|
|
|
let queue_num = 2; // for simplicity
|
|
let mut driver = VirtIONet {
|
|
interrupt: node.prop_u32("interrupts").unwrap(),
|
|
interrupt_parent: node.prop_u32("interrupt-parent").unwrap(),
|
|
header: vaddr as usize,
|
|
mac: EthernetAddress(mac),
|
|
queues: [
|
|
VirtIOVirtqueue::new(header, VIRTIO_QUEUE_RECEIVE, queue_num),
|
|
VirtIOVirtqueue::new(header, VIRTIO_QUEUE_TRANSMIT, queue_num),
|
|
],
|
|
};
|
|
|
|
// allocate a page for buffer
|
|
let page = unsafe {
|
|
HEAP_ALLOCATOR.alloc_zeroed(Layout::from_size_align(PAGE_SIZE, PAGE_SIZE).unwrap())
|
|
} as usize;
|
|
let input = unsafe { slice::from_raw_parts(page as *const u8, PAGE_SIZE) };
|
|
driver.queues[VIRTIO_QUEUE_RECEIVE].add_and_notify(&[input], &[], 0);
|
|
|
|
header.status.write(VirtIODeviceStatus::DRIVER_OK.bits());
|
|
|
|
let net_driver = Arc::new(VirtIONetDriver(Arc::new(Mutex::new(driver))));
|
|
|
|
DRIVERS.write().push(net_driver.clone());
|
|
NET_DRIVERS.write().push(net_driver);
|
|
}
|