net: Basic UDP/ICMP over IPv4 networking using virtio-net
This commit is contained in:
parent
9bd29970f8
commit
5d8067991d
1055
Cargo.lock
generated
Normal file
1055
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
@ -20,6 +20,7 @@ device-api-macros = { path = "lib/device-api/macros" }
|
||||
|
||||
# Drivers
|
||||
ygg_driver_block = { path = "driver/block/core" }
|
||||
ygg_driver_net_core = { path = "driver/net/core" }
|
||||
kernel-fs = { path = "driver/fs/kernel-fs" }
|
||||
memfs = { path = "driver/fs/memfs" }
|
||||
|
||||
@ -57,6 +58,7 @@ xhci_lib = { git = "https://github.com/rust-osdev/xhci.git", package = "xhci" }
|
||||
ygg_driver_pci = { path = "driver/bus/pci" }
|
||||
ygg_driver_nvme = { path = "driver/block/nvme" }
|
||||
ygg_driver_ahci = { path = "driver/block/ahci" }
|
||||
ygg_driver_virtio_net = { path = "driver/virtio/net", features = ["pci"] }
|
||||
|
||||
[features]
|
||||
default = ["fb_console"]
|
||||
|
@ -11,12 +11,48 @@ use yggdrasil_abi::error::Error;
|
||||
use super::{PciCapability, PciCapabilityId, PciConfigurationSpace};
|
||||
use crate::PciBaseAddress;
|
||||
|
||||
pub trait VirtioCapabilityData<'s, S: PciConfigurationSpace + ?Sized + 's>: Sized {
|
||||
fn from_space_offset(space: &'s S, offset: usize) -> Self;
|
||||
|
||||
fn space(&self) -> &'s S;
|
||||
fn offset(&self) -> usize;
|
||||
|
||||
fn bar_index(&self) -> Option<usize> {
|
||||
let value = self.space().read_u8(self.offset() + 4);
|
||||
(value <= 0x5).then_some(value as _)
|
||||
}
|
||||
|
||||
fn bar_offset(&self) -> usize {
|
||||
let value = self.space().read_u32(self.offset() + 8);
|
||||
value as _
|
||||
}
|
||||
|
||||
fn length(&self) -> usize {
|
||||
let value = self.space().read_u32(self.offset() + 12);
|
||||
value as _
|
||||
}
|
||||
}
|
||||
|
||||
pub trait VirtioCapability {
|
||||
const CFG_TYPE: u8;
|
||||
const MIN_LEN: usize = 0;
|
||||
type Output<'a, S: PciConfigurationSpace + ?Sized + 'a>: VirtioCapabilityData<'a, S>;
|
||||
}
|
||||
|
||||
/// MSI-X capability query
|
||||
pub struct MsiXCapability;
|
||||
|
||||
/// MSI capability query
|
||||
pub struct MsiCapability;
|
||||
|
||||
// VirtIO-over-PCI capabilities
|
||||
/// VirtIO PCI configuration access
|
||||
pub struct VirtioDeviceConfigCapability;
|
||||
/// VirtIO common configuration
|
||||
pub struct VirtioCommonConfigCapability;
|
||||
/// VirtIO notify configuration
|
||||
pub struct VirtioNotifyConfigCapability;
|
||||
|
||||
/// Represents an entry in MSI-X vector table
|
||||
#[repr(C)]
|
||||
pub struct MsiXEntry {
|
||||
@ -44,6 +80,39 @@ pub struct MsiData<'s, S: PciConfigurationSpace + ?Sized + 's> {
|
||||
offset: usize,
|
||||
}
|
||||
|
||||
pub struct VirtioDeviceConfigData<'s, S: PciConfigurationSpace + ?Sized + 's> {
|
||||
space: &'s S,
|
||||
offset: usize,
|
||||
}
|
||||
|
||||
pub struct VirtioCommonConfigData<'s, S: PciConfigurationSpace + ?Sized + 's> {
|
||||
space: &'s S,
|
||||
offset: usize,
|
||||
}
|
||||
|
||||
pub struct VirtioNotifyConfigData<'s, S: PciConfigurationSpace + ?Sized + 's> {
|
||||
space: &'s S,
|
||||
offset: usize,
|
||||
}
|
||||
|
||||
impl<T: VirtioCapability> PciCapability for T {
|
||||
const ID: PciCapabilityId = PciCapabilityId::VendorSpecific;
|
||||
type CapabilityData<'a, S: PciConfigurationSpace + ?Sized + 'a> = T::Output<'a, S>;
|
||||
|
||||
fn check<S: PciConfigurationSpace + ?Sized>(space: &S, offset: usize, len: usize) -> bool {
|
||||
let cfg_type = space.read_u8(offset + 3);
|
||||
cfg_type == T::CFG_TYPE && len >= T::MIN_LEN
|
||||
}
|
||||
|
||||
fn data<'s, S: PciConfigurationSpace + ?Sized + 's>(
|
||||
space: &'s S,
|
||||
offset: usize,
|
||||
_len: usize,
|
||||
) -> Self::CapabilityData<'s, S> {
|
||||
T::Output::from_space_offset(space, offset)
|
||||
}
|
||||
}
|
||||
|
||||
impl PciCapability for MsiXCapability {
|
||||
const ID: PciCapabilityId = PciCapabilityId::MsiX;
|
||||
type CapabilityData<'a, S: PciConfigurationSpace + ?Sized + 'a> = MsiXData<'a, S>;
|
||||
@ -51,6 +120,7 @@ impl PciCapability for MsiXCapability {
|
||||
fn data<'s, S: PciConfigurationSpace + ?Sized + 's>(
|
||||
space: &'s S,
|
||||
offset: usize,
|
||||
_len: usize,
|
||||
) -> Self::CapabilityData<'s, S> {
|
||||
MsiXData { space, offset }
|
||||
}
|
||||
@ -63,11 +133,82 @@ impl PciCapability for MsiCapability {
|
||||
fn data<'s, S: PciConfigurationSpace + ?Sized + 's>(
|
||||
space: &'s S,
|
||||
offset: usize,
|
||||
_len: usize,
|
||||
) -> Self::CapabilityData<'s, S> {
|
||||
MsiData { space, offset }
|
||||
}
|
||||
}
|
||||
|
||||
impl VirtioCapability for VirtioDeviceConfigCapability {
|
||||
const CFG_TYPE: u8 = 0x04;
|
||||
type Output<'a, S: PciConfigurationSpace + ?Sized + 'a> = VirtioDeviceConfigData<'a, S>;
|
||||
}
|
||||
|
||||
impl<'s, S: PciConfigurationSpace + ?Sized + 's> VirtioCapabilityData<'s, S>
|
||||
for VirtioDeviceConfigData<'s, S>
|
||||
{
|
||||
fn from_space_offset(space: &'s S, offset: usize) -> Self {
|
||||
Self { space, offset }
|
||||
}
|
||||
|
||||
fn space(&self) -> &'s S {
|
||||
self.space
|
||||
}
|
||||
|
||||
fn offset(&self) -> usize {
|
||||
self.offset
|
||||
}
|
||||
}
|
||||
|
||||
impl VirtioCapability for VirtioCommonConfigCapability {
|
||||
const CFG_TYPE: u8 = 0x01;
|
||||
type Output<'a, S: PciConfigurationSpace + ?Sized + 'a> = VirtioCommonConfigData<'a, S>;
|
||||
}
|
||||
|
||||
impl<'s, S: PciConfigurationSpace + ?Sized + 's> VirtioCapabilityData<'s, S>
|
||||
for VirtioCommonConfigData<'s, S>
|
||||
{
|
||||
fn from_space_offset(space: &'s S, offset: usize) -> Self {
|
||||
Self { space, offset }
|
||||
}
|
||||
|
||||
fn space(&self) -> &'s S {
|
||||
self.space
|
||||
}
|
||||
|
||||
fn offset(&self) -> usize {
|
||||
self.offset
|
||||
}
|
||||
}
|
||||
|
||||
impl VirtioCapability for VirtioNotifyConfigCapability {
|
||||
const CFG_TYPE: u8 = 0x02;
|
||||
const MIN_LEN: usize = 0x14;
|
||||
type Output<'a, S: PciConfigurationSpace + ?Sized + 'a> = VirtioNotifyConfigData<'a, S>;
|
||||
}
|
||||
|
||||
impl<'s, S: PciConfigurationSpace + ?Sized + 's> VirtioNotifyConfigData<'s, S> {
|
||||
pub fn offset_multiplier(&self) -> usize {
|
||||
self.space.read_u32(self.offset + 16) as usize
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s, S: PciConfigurationSpace + ?Sized + 's> VirtioCapabilityData<'s, S>
|
||||
for VirtioNotifyConfigData<'s, S>
|
||||
{
|
||||
fn from_space_offset(space: &'s S, offset: usize) -> Self {
|
||||
Self { space, offset }
|
||||
}
|
||||
|
||||
fn space(&self) -> &'s S {
|
||||
self.space
|
||||
}
|
||||
|
||||
fn offset(&self) -> usize {
|
||||
self.offset
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s, S: PciConfigurationSpace + ?Sized + 's> MsiXData<'s, S> {
|
||||
// TODO use pending bits as well
|
||||
/// Maps and returns the vector table associated with the device's MSI-X capability
|
||||
|
@ -73,6 +73,8 @@ pub enum PciBaseAddress {
|
||||
pub enum PciCapabilityId {
|
||||
/// MSI (32-bit or 64-bit)
|
||||
Msi = 0x05,
|
||||
/// Vendor-specific capability
|
||||
VendorSpecific = 0x09,
|
||||
/// MSI-X
|
||||
MsiX = 0x11,
|
||||
/// Unknown capability missing from this list
|
||||
@ -86,10 +88,15 @@ pub trait PciCapability {
|
||||
/// Wrapper for accessing the capability data structure
|
||||
type CapabilityData<'a, S: PciConfigurationSpace + ?Sized + 'a>;
|
||||
|
||||
fn check<S: PciConfigurationSpace + ?Sized>(space: &S, offset: usize, len: usize) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
/// Constructs an access wrapper for this capability with given offset
|
||||
fn data<'s, S: PciConfigurationSpace + ?Sized + 's>(
|
||||
space: &'s S,
|
||||
offset: usize,
|
||||
len: usize,
|
||||
) -> Self::CapabilityData<'s, S>;
|
||||
}
|
||||
|
||||
@ -104,6 +111,7 @@ pub struct PciDeviceInfo {
|
||||
|
||||
pub enum PciMatch {
|
||||
Generic(fn(&PciDeviceInfo) -> bool),
|
||||
Vendor(u16, u16),
|
||||
Class(u8, Option<u8>, Option<u8>),
|
||||
}
|
||||
|
||||
@ -134,6 +142,15 @@ pub struct PciBusManager {
|
||||
segments: Vec<PciBusSegment>,
|
||||
}
|
||||
|
||||
impl PciBaseAddress {
|
||||
pub fn as_memory(self) -> usize {
|
||||
match self {
|
||||
Self::Memory(address) => address,
|
||||
_ => panic!("Not a memory BAR"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PciBusSegment {
|
||||
fn probe_config_space(&self, address: PciAddress) -> Result<Option<PciConfigSpace>, Error> {
|
||||
match self.ecam_phys_base {
|
||||
@ -344,6 +361,9 @@ impl PciMatch {
|
||||
pub fn check_device(&self, info: &PciDeviceInfo, class: u8, subclass: u8, prog_if: u8) -> bool {
|
||||
match self {
|
||||
Self::Generic(f) => f(info),
|
||||
&Self::Vendor(vendor_, device_) => {
|
||||
info.config_space.vendor_id() == vendor_ && info.config_space.device_id() == device_
|
||||
}
|
||||
&Self::Class(class_, Some(subclass_), Some(prog_if_)) => {
|
||||
class_ == class && subclass_ == subclass && prog_if_ == prog_if
|
||||
}
|
||||
@ -367,6 +387,19 @@ pub fn register_class_driver(
|
||||
});
|
||||
}
|
||||
|
||||
pub fn register_vendor_driver(
|
||||
name: &'static str,
|
||||
vendor_id: u16,
|
||||
device_id: u16,
|
||||
probe: fn(&PciDeviceInfo) -> Result<&'static dyn Device, Error>,
|
||||
) {
|
||||
PCI_DRIVERS.lock().push(PciDriver {
|
||||
name,
|
||||
check: PciMatch::Vendor(vendor_id, device_id),
|
||||
probe,
|
||||
});
|
||||
}
|
||||
|
||||
pub fn register_generic_driver(
|
||||
name: &'static str,
|
||||
check: fn(&PciDeviceInfo) -> bool,
|
||||
|
@ -79,12 +79,13 @@ pub struct CapabilityIterator<'s, S: PciConfigurationSpace + ?Sized> {
|
||||
}
|
||||
|
||||
impl<'s, S: PciConfigurationSpace + ?Sized> Iterator for CapabilityIterator<'s, S> {
|
||||
type Item = (PciCapabilityId, usize);
|
||||
type Item = (PciCapabilityId, usize, usize);
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
let offset = self.current? & !0x3;
|
||||
|
||||
let id = unsafe { core::mem::transmute(self.space.read_u8(offset)) };
|
||||
let len = self.space.read_u8(offset + 2);
|
||||
let next_pointer = self.space.read_u8(offset + 1);
|
||||
|
||||
self.current = if next_pointer != 0 {
|
||||
@ -93,7 +94,7 @@ impl<'s, S: PciConfigurationSpace + ?Sized> Iterator for CapabilityIterator<'s,
|
||||
None
|
||||
};
|
||||
|
||||
Some((id, offset))
|
||||
Some((id, offset, len as usize))
|
||||
}
|
||||
}
|
||||
|
||||
@ -301,9 +302,9 @@ pub trait PciConfigurationSpace {
|
||||
|
||||
/// Locates a capability within this configuration space
|
||||
fn capability<C: PciCapability>(&self) -> Option<C::CapabilityData<'_, Self>> {
|
||||
self.capability_iter().find_map(|(id, offset)| {
|
||||
if id == C::ID {
|
||||
Some(C::data(self, offset))
|
||||
self.capability_iter().find_map(|(id, offset, len)| {
|
||||
if id == C::ID && C::check(self, offset, len) {
|
||||
Some(C::data(self, offset, len))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
|
@ -1,9 +1,15 @@
|
||||
//! Device virtual file system
|
||||
use core::sync::atomic::{AtomicUsize, Ordering};
|
||||
use core::{
|
||||
net::IpAddr,
|
||||
sync::atomic::{AtomicUsize, Ordering},
|
||||
};
|
||||
|
||||
use alloc::{format, string::String};
|
||||
use alloc::{format, string::String, sync::Arc};
|
||||
use kernel_util::util::OneTimeInit;
|
||||
use vfs::{impls::MemoryDirectory, CharDevice, Node, NodeFlags, NodeRef};
|
||||
use vfs::{
|
||||
impls::{mdir, FnValueNode, MemoryDirectory, ReadOnlyFnValueNode},
|
||||
CharDevice, Node, NodeFlags, NodeRef,
|
||||
};
|
||||
use ygg_driver_block::BlockDevice;
|
||||
use yggdrasil_abi::error::Error;
|
||||
|
||||
@ -16,11 +22,28 @@ pub enum CharDeviceType {
|
||||
TtySerial,
|
||||
}
|
||||
|
||||
/// Describes the kind of a network device
|
||||
#[derive(Debug)]
|
||||
pub enum NetworkDeviceType {
|
||||
/// Ethernet device
|
||||
Ethernet,
|
||||
}
|
||||
|
||||
pub trait NetworkConfigInterface: Send + Sync {
|
||||
fn address(&self) -> Result<IpAddr, Error>;
|
||||
fn set_address(&self, addr: IpAddr) -> Result<(), Error>;
|
||||
|
||||
fn mac(&self) -> [u8; 6];
|
||||
}
|
||||
|
||||
static DEVFS_ROOT: OneTimeInit<NodeRef> = OneTimeInit::new();
|
||||
static NET_ROOT: OneTimeInit<NodeRef> = OneTimeInit::new();
|
||||
|
||||
/// Sets up the device filesystem
|
||||
pub fn init() {
|
||||
let root = MemoryDirectory::empty();
|
||||
let net = MemoryDirectory::empty();
|
||||
NET_ROOT.init(net.clone());
|
||||
let root = mdir([("net", net)]);
|
||||
DEVFS_ROOT.init(root);
|
||||
}
|
||||
|
||||
@ -55,6 +78,59 @@ pub fn add_named_block_device<S: Into<String>>(
|
||||
DEVFS_ROOT.get().add_child(name, node)
|
||||
}
|
||||
|
||||
pub fn add_network_config<S: Into<String>>(name: S, node: NodeRef) -> Result<(), Error> {
|
||||
let name = name.into();
|
||||
NET_ROOT.get().add_child(name, node)
|
||||
}
|
||||
|
||||
pub fn add_named_network_device<S: Into<String>>(
|
||||
name: S,
|
||||
dev: Arc<dyn NetworkConfigInterface>,
|
||||
) -> Result<(), Error> {
|
||||
let name = name.into();
|
||||
log::info!("Add network device: {}", name);
|
||||
|
||||
let read_dev = dev.clone();
|
||||
let write_dev = dev.clone();
|
||||
let address = FnValueNode::new(
|
||||
move || read_dev.address(),
|
||||
move |value| write_dev.set_address(value),
|
||||
);
|
||||
|
||||
let read_dev = dev.clone();
|
||||
let mac = ReadOnlyFnValueNode::new(move || {
|
||||
let bytes = read_dev.mac();
|
||||
let mut out = String::new();
|
||||
for (i, byte) in bytes.into_iter().enumerate() {
|
||||
if i != 0 {
|
||||
out.push(':');
|
||||
}
|
||||
out.push_str(&format!("{:02X}", byte));
|
||||
}
|
||||
Ok(out)
|
||||
});
|
||||
|
||||
let dev_dir = mdir([("address", address), ("mac", mac)]);
|
||||
|
||||
NET_ROOT.get().add_child(name, dev_dir)
|
||||
}
|
||||
|
||||
pub fn add_network_device(
|
||||
dev: Arc<dyn NetworkConfigInterface>,
|
||||
kind: NetworkDeviceType,
|
||||
) -> Result<(), Error> {
|
||||
static ETH_COUNT: AtomicUsize = AtomicUsize::new(0);
|
||||
|
||||
let (count, prefix) = match kind {
|
||||
NetworkDeviceType::Ethernet => (Ð_COUNT, "eth"),
|
||||
};
|
||||
|
||||
let value = count.fetch_add(1, Ordering::AcqRel);
|
||||
let name = format!("{}{}", prefix, value);
|
||||
|
||||
add_named_network_device(name, dev)
|
||||
}
|
||||
|
||||
pub fn add_block_device_partition<S: Into<String>>(
|
||||
base_name: S,
|
||||
index: usize,
|
||||
|
16
driver/net/core/Cargo.toml
Normal file
16
driver/net/core/Cargo.toml
Normal file
@ -0,0 +1,16 @@
|
||||
[package]
|
||||
name = "ygg_driver_net_core"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
yggdrasil-abi = { git = "https://git.alnyan.me/yggdrasil/yggdrasil-abi.git" }
|
||||
kernel-util = { path = "../../../lib/kernel-util" }
|
||||
vfs = { path = "../../../lib/vfs" }
|
||||
|
||||
kernel-fs = { path = "../../fs/kernel-fs" }
|
||||
|
||||
log = "0.4.20"
|
||||
bytemuck = { version = "1.14.0", features = ["derive"] }
|
100
driver/net/core/src/ethernet.rs
Normal file
100
driver/net/core/src/ethernet.rs
Normal file
@ -0,0 +1,100 @@
|
||||
use core::{fmt, mem::size_of};
|
||||
|
||||
use bytemuck::{Pod, Zeroable};
|
||||
use kernel_util::mem::PageBox;
|
||||
use yggdrasil_abi::error::Error;
|
||||
|
||||
use crate::{
|
||||
interface::{self, NetworkInterface},
|
||||
l3,
|
||||
types::{MacAddress, NetValue, Value},
|
||||
};
|
||||
|
||||
pub struct L2Packet {
|
||||
pub interface_id: u32,
|
||||
|
||||
pub source_address: MacAddress,
|
||||
pub destination_address: MacAddress,
|
||||
|
||||
pub l2_offset: usize,
|
||||
pub l3_offset: usize,
|
||||
|
||||
pub data: PageBox<[u8]>,
|
||||
}
|
||||
|
||||
impl L2Packet {
|
||||
pub fn ethernet_frame(&self) -> &EthernetFrame {
|
||||
bytemuck::from_bytes(
|
||||
&self.data[self.l2_offset..self.l2_offset + size_of::<EthernetFrame>()],
|
||||
)
|
||||
}
|
||||
|
||||
pub fn l2_data(&self) -> &[u8] {
|
||||
&self.data[self.l3_offset..]
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Pod, Zeroable)]
|
||||
#[repr(C)]
|
||||
pub struct EthernetFrame {
|
||||
pub destination_mac: MacAddress,
|
||||
pub source_mac: MacAddress,
|
||||
pub ethertype: Value<EtherType>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug, Pod, Zeroable)]
|
||||
#[repr(transparent)]
|
||||
pub struct EtherType(u16);
|
||||
|
||||
impl EtherType {
|
||||
pub const ARP: Self = Self(0x0806);
|
||||
pub const IPV4: Self = Self(0x0800);
|
||||
}
|
||||
|
||||
impl NetValue for EtherType {
|
||||
fn to_network_order(self) -> Value<Self> {
|
||||
Value(Self(self.0.to_be()))
|
||||
}
|
||||
|
||||
fn from_network_order(value: Value<Self>) -> Self {
|
||||
Self(u16::from_be(value.0 .0))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn send_l2<T: Pod>(
|
||||
interface: &NetworkInterface,
|
||||
source_mac: MacAddress,
|
||||
destination_mac: MacAddress,
|
||||
ethertype: EtherType,
|
||||
l2_data: &T,
|
||||
) -> Result<(), Error> {
|
||||
let l2_frame = EthernetFrame {
|
||||
source_mac,
|
||||
destination_mac,
|
||||
ethertype: ethertype.to_network_order(),
|
||||
};
|
||||
|
||||
log::debug!(
|
||||
"send_l2: {} -> {}",
|
||||
l2_frame.source_mac,
|
||||
l2_frame.destination_mac
|
||||
);
|
||||
|
||||
interface.send_l2(&l2_frame, bytemuck::bytes_of(l2_data))
|
||||
}
|
||||
|
||||
pub fn handle(packet: L2Packet) {
|
||||
let frame = packet.ethernet_frame();
|
||||
let ty = EtherType::from_network_order(frame.ethertype);
|
||||
|
||||
match ty {
|
||||
EtherType::ARP => l3::arp::handle_packet(packet),
|
||||
EtherType::IPV4 => l3::ip::handle_v4_packet(packet),
|
||||
p => {
|
||||
log::debug!(
|
||||
"Unrecognized L2 protocol: {:#06x}",
|
||||
bytemuck::cast::<_, u16>(p)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
116
driver/net/core/src/interface.rs
Normal file
116
driver/net/core/src/interface.rs
Normal file
@ -0,0 +1,116 @@
|
||||
use core::{
|
||||
mem::size_of,
|
||||
net::{IpAddr, Ipv4Addr},
|
||||
sync::atomic::{AtomicU32, Ordering},
|
||||
};
|
||||
|
||||
use alloc::{collections::BTreeMap, sync::Arc, vec};
|
||||
use bytemuck::Pod;
|
||||
use kernel_fs::devfs::{self, NetworkConfigInterface, NetworkDeviceType};
|
||||
// TODO: link state management?
|
||||
use kernel_util::{
|
||||
mem::PageBox,
|
||||
sync::{mutex::Mutex, spin_rwlock::IrqSafeRwLock},
|
||||
};
|
||||
use yggdrasil_abi::error::Error;
|
||||
|
||||
use crate::{
|
||||
ethernet::EthernetFrame,
|
||||
l3::{self, arp::ARP_TABLE, IpFrame, Route},
|
||||
types::{MacAddress, SubnetAddress, SubnetV4Address},
|
||||
};
|
||||
|
||||
pub trait NetworkDevice: Sync {
|
||||
fn transmit(&self, packet: PageBox<[u8]>) -> Result<(), Error>;
|
||||
fn packet_prefix_size(&self) -> usize;
|
||||
|
||||
fn read_hardware_address(&self) -> MacAddress;
|
||||
}
|
||||
|
||||
pub struct NetworkInterface {
|
||||
pub(crate) device: &'static dyn NetworkDevice,
|
||||
pub(crate) mac: MacAddress,
|
||||
|
||||
pub(crate) address: IrqSafeRwLock<Option<IpAddr>>,
|
||||
pub(crate) id: u32,
|
||||
}
|
||||
|
||||
impl NetworkConfigInterface for NetworkInterface {
|
||||
fn address(&self) -> Result<IpAddr, Error> {
|
||||
if let Some(address) = self.address.read().as_ref() {
|
||||
Ok(*address)
|
||||
} else {
|
||||
Err(Error::InvalidOperation)
|
||||
}
|
||||
}
|
||||
|
||||
fn set_address(&self, addr: IpAddr) -> Result<(), Error> {
|
||||
// Remove old ARP entry, if exists
|
||||
ARP_TABLE.write().remove_ip(self.id, addr);
|
||||
*self.address.write() = Some(addr);
|
||||
ARP_TABLE.write().insert_ip(self.id, self.mac, addr, true);
|
||||
match addr {
|
||||
IpAddr::V4(v4) => {
|
||||
l3::add_route(Route {
|
||||
subnet: SubnetAddress::V4(SubnetV4Address::from_address_mask(v4, 24)),
|
||||
interface: self.id,
|
||||
// TODO manual configuration for this
|
||||
gateway: Some(IpAddr::V4(Ipv4Addr::new(11, 0, 0, 1))),
|
||||
});
|
||||
}
|
||||
IpAddr::V6(_) => todo!(),
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn mac(&self) -> [u8; 6] {
|
||||
self.mac.into()
|
||||
}
|
||||
}
|
||||
|
||||
static ETH_INTERFACES: IrqSafeRwLock<BTreeMap<u32, Arc<NetworkInterface>>> =
|
||||
IrqSafeRwLock::new(BTreeMap::new());
|
||||
static ETH_INTERFACE_ID: AtomicU32 = AtomicU32::new(1);
|
||||
|
||||
impl NetworkInterface {
|
||||
pub fn get(id: u32) -> Result<Arc<Self>, Error> {
|
||||
ETH_INTERFACES
|
||||
.read()
|
||||
.get(&id)
|
||||
.cloned()
|
||||
.ok_or(Error::DoesNotExist)
|
||||
}
|
||||
|
||||
pub fn send_l2(&self, l2_frame: &EthernetFrame, l2_data: &[u8]) -> Result<(), Error> {
|
||||
let l2_offset = self.device.packet_prefix_size();
|
||||
let l2_data_offset = l2_offset + size_of::<EthernetFrame>();
|
||||
|
||||
let mut packet = PageBox::new_slice(0, l2_data_offset + l2_data.len())?;
|
||||
|
||||
packet[l2_offset..l2_data_offset].copy_from_slice(bytemuck::bytes_of(l2_frame));
|
||||
packet[l2_data_offset..].copy_from_slice(l2_data);
|
||||
|
||||
self.device.transmit(packet)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn register_interface(dev: &'static dyn NetworkDevice) -> u32 {
|
||||
let mac = dev.read_hardware_address();
|
||||
|
||||
let id = ETH_INTERFACE_ID.fetch_add(1, Ordering::SeqCst);
|
||||
|
||||
let iface = NetworkInterface {
|
||||
device: dev,
|
||||
mac,
|
||||
address: IrqSafeRwLock::new(None),
|
||||
id,
|
||||
};
|
||||
log::info!("Registered network interface #{}: {}", id, mac);
|
||||
|
||||
let interface = Arc::new(iface);
|
||||
|
||||
devfs::add_network_device(interface.clone(), NetworkDeviceType::Ethernet).unwrap();
|
||||
ETH_INTERFACES.write().insert(id, interface);
|
||||
|
||||
id
|
||||
}
|
142
driver/net/core/src/l3/arp.rs
Normal file
142
driver/net/core/src/l3/arp.rs
Normal file
@ -0,0 +1,142 @@
|
||||
use core::{
|
||||
borrow::Borrow,
|
||||
mem::size_of,
|
||||
net::{IpAddr, Ipv4Addr},
|
||||
};
|
||||
|
||||
use alloc::{collections::BTreeMap, sync::Arc};
|
||||
use bytemuck::{Pod, Zeroable};
|
||||
use kernel_util::{mem::PageBox, sync::spin_rwlock::IrqSafeRwLock};
|
||||
use yggdrasil_abi::error::Error;
|
||||
|
||||
use crate::{
|
||||
ethernet::{self, EtherType, EthernetFrame},
|
||||
interface::NetworkInterface,
|
||||
types::{MacAddress, NetValue, Value},
|
||||
L2Packet,
|
||||
};
|
||||
|
||||
#[derive(Clone, Copy, Debug, Pod, Zeroable)]
|
||||
#[repr(C, packed)]
|
||||
struct ArpFrame {
|
||||
pub hardware_type: Value<u16>,
|
||||
pub protocol: Value<EtherType>,
|
||||
pub hardware_size: u8,
|
||||
pub protocol_size: u8,
|
||||
pub opcode: Value<u16>,
|
||||
pub sender_mac: MacAddress,
|
||||
pub sender_ip: Value<u32>,
|
||||
pub target_mac: MacAddress,
|
||||
// TODO handle IPv6
|
||||
pub target_ip: Value<u32>,
|
||||
}
|
||||
|
||||
pub struct ArpTable {
|
||||
entries_v4: BTreeMap<(u32, Ipv4Addr), (MacAddress, bool)>,
|
||||
reverse_v4: BTreeMap<(u32, MacAddress), (Ipv4Addr, bool)>,
|
||||
}
|
||||
|
||||
impl ArpTable {
|
||||
pub const fn new() -> Self {
|
||||
Self {
|
||||
reverse_v4: BTreeMap::new(),
|
||||
entries_v4: BTreeMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
// TODO multiple IPs per MAC?
|
||||
pub fn insert_v4(&mut self, interface: u32, mac: MacAddress, ip: Ipv4Addr, own: bool) {
|
||||
if !self.reverse_v4.contains_key(&(interface, mac)) {
|
||||
log::debug!("Insert {} <-> {}", mac, ip);
|
||||
}
|
||||
|
||||
self.reverse_v4.insert((interface, mac), (ip, own));
|
||||
self.entries_v4.insert((interface, ip), (mac, own));
|
||||
}
|
||||
|
||||
pub fn insert_ip(&mut self, interface: u32, mac: MacAddress, ip: IpAddr, own: bool) {
|
||||
match ip {
|
||||
IpAddr::V4(v4) => self.insert_v4(interface, mac, v4, own),
|
||||
IpAddr::V6(_) => todo!(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn remove_ip(&mut self, interface: u32, ip: IpAddr) {
|
||||
match ip {
|
||||
IpAddr::V4(v4) => {
|
||||
let entry = self.entries_v4.remove(&(interface, v4));
|
||||
if let Some((mac, _)) = entry {
|
||||
self.reverse_v4.remove(&(interface, mac)).unwrap();
|
||||
}
|
||||
}
|
||||
IpAddr::V6(_) => todo!(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_v4(&self, interface: u32, ip: Ipv4Addr) -> Option<(MacAddress, bool)> {
|
||||
self.entries_v4.get(&(interface, ip)).cloned()
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) static ARP_TABLE: IrqSafeRwLock<ArpTable> = IrqSafeRwLock::new(ArpTable::new());
|
||||
|
||||
pub fn lookup(interface: u32, ip: IpAddr) -> Option<MacAddress> {
|
||||
// TODO send a request if MAC is not yet in the table
|
||||
match ip {
|
||||
IpAddr::V4(v4) => Some(ARP_TABLE.read().get_v4(interface, v4)?.0),
|
||||
IpAddr::V6(_) => todo!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn send_reply(interface_id: u32, arp: &ArpFrame, target_mac: MacAddress) -> Result<(), Error> {
|
||||
let interface = NetworkInterface::get(interface_id)?;
|
||||
let reply = ArpFrame {
|
||||
protocol: arp.protocol,
|
||||
hardware_type: arp.hardware_type,
|
||||
hardware_size: arp.hardware_size,
|
||||
protocol_size: arp.protocol_size,
|
||||
opcode: 2u16.to_network_order(),
|
||||
|
||||
sender_mac: target_mac,
|
||||
sender_ip: arp.target_ip,
|
||||
|
||||
target_ip: arp.sender_ip,
|
||||
target_mac: arp.sender_mac,
|
||||
};
|
||||
|
||||
ethernet::send_l2(
|
||||
&interface,
|
||||
target_mac,
|
||||
arp.sender_mac,
|
||||
EtherType::ARP,
|
||||
&reply,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn handle_packet(packet: L2Packet) {
|
||||
let arp: &ArpFrame = bytemuck::from_bytes(&packet.l2_data()[..size_of::<ArpFrame>()]);
|
||||
let proto = EtherType::from_network_order(arp.protocol);
|
||||
let opcode = u16::from_network_order(arp.opcode);
|
||||
|
||||
let (target_address, sender_address) = match proto {
|
||||
EtherType::IPV4 => (
|
||||
Ipv4Addr::from(u32::from_network_order(arp.target_ip)),
|
||||
Ipv4Addr::from(u32::from_network_order(arp.sender_ip)),
|
||||
),
|
||||
_ => {
|
||||
log::warn!("TODO: unhandled ARP proto: {:#x?}", proto);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let mut table = ARP_TABLE.write();
|
||||
table.insert_v4(packet.interface_id, arp.sender_mac, sender_address, false);
|
||||
|
||||
if opcode == 1 {
|
||||
// Don't answer with non-owned addresses
|
||||
if let Some((mac, true)) = table.get_v4(packet.interface_id, target_address) {
|
||||
// Reply with own address
|
||||
send_reply(packet.interface_id, arp, mac).ok();
|
||||
}
|
||||
}
|
||||
}
|
148
driver/net/core/src/l3/ip.rs
Normal file
148
driver/net/core/src/l3/ip.rs
Normal file
@ -0,0 +1,148 @@
|
||||
use core::{
|
||||
fmt,
|
||||
mem::size_of,
|
||||
net::{IpAddr, Ipv4Addr},
|
||||
};
|
||||
|
||||
use alloc::sync::Arc;
|
||||
use bytemuck::{Pod, Zeroable};
|
||||
use yggdrasil_abi::error::Error;
|
||||
|
||||
use crate::{
|
||||
ethernet::EthernetFrame,
|
||||
interface::NetworkInterface,
|
||||
l3,
|
||||
l4::udp,
|
||||
types::{InetChecksum, NetValue, Value},
|
||||
L2Packet, L3Packet, ACCEPT_QUEUE,
|
||||
};
|
||||
|
||||
use super::IpFrame;
|
||||
|
||||
#[derive(Clone, Copy, Debug, Pod, Zeroable)]
|
||||
#[repr(C)]
|
||||
pub struct Ipv4Frame {
|
||||
pub version_length: u8,
|
||||
pub dscp_flags: u8,
|
||||
pub total_length: Value<u16>,
|
||||
pub id: Value<u16>,
|
||||
pub flags_frag: Value<u16>,
|
||||
pub ttl: u8,
|
||||
pub protocol: Protocol,
|
||||
pub header_checksum: Value<u16>,
|
||||
pub source_address: Value<u32>,
|
||||
pub destination_address: Value<u32>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Pod, Zeroable)]
|
||||
#[repr(transparent)]
|
||||
pub struct Protocol(pub u8);
|
||||
|
||||
impl Protocol {
|
||||
pub const ICMP: Self = Self(1);
|
||||
pub const UDP: Self = Self(17);
|
||||
}
|
||||
|
||||
impl fmt::Debug for Protocol {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match *self {
|
||||
Self::ICMP => f.write_str("ICMP"),
|
||||
Self::UDP => f.write_str("UDP"),
|
||||
_ => f.write_str("<unknown-l4-proto>"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Ipv4Frame {
|
||||
fn header_length(&self) -> usize {
|
||||
core::cmp::min(
|
||||
(self.version_length & 0xF) << 2,
|
||||
size_of::<Ipv4Frame>() as u8,
|
||||
) as usize
|
||||
}
|
||||
|
||||
fn total_length(&self) -> usize {
|
||||
u16::from_network_order(self.total_length) as usize
|
||||
}
|
||||
}
|
||||
|
||||
impl IpFrame for Ipv4Frame {
|
||||
fn destination_ip(&self) -> IpAddr {
|
||||
IpAddr::V4(Ipv4Addr::from(u32::from_network_order(
|
||||
self.destination_address,
|
||||
)))
|
||||
}
|
||||
|
||||
fn source_ip(&self) -> IpAddr {
|
||||
IpAddr::V4(Ipv4Addr::from(u32::from_network_order(self.source_address)))
|
||||
}
|
||||
|
||||
fn data_length(&self) -> usize {
|
||||
self.total_length()
|
||||
.checked_sub(self.header_length())
|
||||
.unwrap_or(0)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn handle_v4_packet(packet: L2Packet) {
|
||||
let Ok(interface) = NetworkInterface::get(packet.interface_id) else {
|
||||
log::debug!("Invalid interface ID in L2 packet");
|
||||
return;
|
||||
};
|
||||
|
||||
let l2_data = packet.l2_data();
|
||||
let l3_frame: &Ipv4Frame = bytemuck::from_bytes(&l2_data[..size_of::<Ipv4Frame>()]);
|
||||
let header_length = l3_frame.header_length();
|
||||
let l3_data = &l2_data[size_of::<Ipv4Frame>()..];
|
||||
|
||||
let is_input = interface
|
||||
.address
|
||||
.read()
|
||||
.map(|address| address == l3_frame.destination_ip())
|
||||
.unwrap_or(false);
|
||||
|
||||
if is_input {
|
||||
// Extract ports from L4 proto
|
||||
let (source_port, destination_port) = match l3_frame.protocol {
|
||||
Protocol::UDP => {
|
||||
// TODO check size
|
||||
let l4_frame: &udp::UdpFrame =
|
||||
bytemuck::from_bytes(&l3_data[..size_of::<udp::UdpFrame>()]);
|
||||
(
|
||||
Some(u16::from_network_order(l4_frame.source_port)),
|
||||
Some(u16::from_network_order(l4_frame.destination_port)),
|
||||
)
|
||||
}
|
||||
Protocol::ICMP => (None, None),
|
||||
_ => (None, None),
|
||||
};
|
||||
|
||||
let l3_packet = L3Packet {
|
||||
interface_id: packet.interface_id,
|
||||
|
||||
protocol: l3_frame.protocol,
|
||||
|
||||
source_address: l3_frame.source_ip(),
|
||||
destination_address: l3_frame.destination_ip(),
|
||||
|
||||
source_port,
|
||||
destination_port,
|
||||
|
||||
l2_offset: packet.l2_offset,
|
||||
l3_offset: packet.l3_offset,
|
||||
l4_offset: packet.l3_offset + header_length,
|
||||
data_length: l3_frame.data_length(),
|
||||
|
||||
data: packet.data,
|
||||
};
|
||||
|
||||
ACCEPT_QUEUE.receive_packet(l3_packet).ok();
|
||||
} else {
|
||||
// TODO forwarding
|
||||
log::debug!(
|
||||
"Dropped forwarded IPv4: {} -> {}",
|
||||
l3_frame.source_ip(),
|
||||
l3_frame.destination_ip()
|
||||
);
|
||||
}
|
||||
}
|
187
driver/net/core/src/l3/mod.rs
Normal file
187
driver/net/core/src/l3/mod.rs
Normal file
@ -0,0 +1,187 @@
|
||||
use core::{
|
||||
fmt,
|
||||
mem::size_of,
|
||||
net::{IpAddr, Ipv4Addr},
|
||||
str::FromStr,
|
||||
};
|
||||
|
||||
use alloc::{sync::Arc, vec::Vec};
|
||||
use bytemuck::Pod;
|
||||
use kernel_util::{mem::PageBox, sync::spin_rwlock::IrqSafeRwLock};
|
||||
use yggdrasil_abi::error::Error;
|
||||
|
||||
use crate::{
|
||||
ethernet::{EtherType, EthernetFrame},
|
||||
interface::NetworkInterface,
|
||||
l3::ip::Ipv4Frame,
|
||||
l4,
|
||||
types::{InetChecksum, NetValue, SubnetAddress},
|
||||
};
|
||||
|
||||
use self::ip::Protocol;
|
||||
|
||||
pub mod arp;
|
||||
pub mod ip;
|
||||
|
||||
pub struct L3Packet {
|
||||
pub interface_id: u32,
|
||||
|
||||
pub protocol: Protocol,
|
||||
|
||||
pub source_address: IpAddr,
|
||||
pub destination_address: IpAddr,
|
||||
|
||||
pub source_port: Option<u16>,
|
||||
pub destination_port: Option<u16>,
|
||||
|
||||
pub l2_offset: usize,
|
||||
pub l3_offset: usize,
|
||||
pub l4_offset: usize,
|
||||
pub data_length: usize,
|
||||
|
||||
pub data: PageBox<[u8]>,
|
||||
}
|
||||
|
||||
pub trait IpFrame: Pod {
|
||||
fn destination_ip(&self) -> IpAddr;
|
||||
fn source_ip(&self) -> IpAddr;
|
||||
fn data_length(&self) -> usize;
|
||||
}
|
||||
|
||||
// TODO use range map for this?
|
||||
pub struct Route {
|
||||
pub subnet: SubnetAddress,
|
||||
pub interface: u32,
|
||||
pub gateway: Option<IpAddr>,
|
||||
}
|
||||
|
||||
static ROUTES: IrqSafeRwLock<Vec<Route>> = IrqSafeRwLock::new(Vec::new());
|
||||
|
||||
impl L3Packet {
|
||||
pub fn l3_data(&self) -> &[u8] {
|
||||
&self.data[self.l4_offset..]
|
||||
}
|
||||
}
|
||||
|
||||
pub fn lookup_route(address: IpAddr) -> Option<(u32, Option<IpAddr>)> {
|
||||
let routes = ROUTES.read();
|
||||
for route in routes.iter() {
|
||||
if route.subnet.contains(&address) {
|
||||
return Some((route.interface, route.gateway));
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub fn list_routes<F: FnMut(&Route)>(mut f: F) {
|
||||
let routes = ROUTES.read();
|
||||
for route in routes.iter() {
|
||||
f(route);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_route(route: Route) -> Result<(), Error> {
|
||||
// TODO check for conflicts
|
||||
log::debug!("Add route: {}", route);
|
||||
ROUTES.write().push(route);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
impl fmt::Display for Route {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{} ", self.subnet)?;
|
||||
if let Some(gw) = self.gateway {
|
||||
write!(f, " via {}", gw)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn handle_accepted(l3_packet: L3Packet) -> Result<(), Error> {
|
||||
match l3_packet.protocol {
|
||||
Protocol::UDP => l4::udp::handle(l3_packet),
|
||||
Protocol::ICMP => l4::icmp::handle(l3_packet),
|
||||
_ => todo!(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn send_l4_ip<L4: Pod>(
|
||||
destination_ip: IpAddr,
|
||||
protocol: ip::Protocol,
|
||||
l4_frame: &L4,
|
||||
l4_data: &[u8],
|
||||
) -> Result<(), Error> {
|
||||
send_l4_ip_opt(destination_ip, protocol, l4_frame, &[], l4_data)
|
||||
}
|
||||
|
||||
pub fn send_l4_ip_opt<L4: Pod>(
|
||||
destination_ip: IpAddr,
|
||||
protocol: ip::Protocol,
|
||||
l4_frame: &L4,
|
||||
l4_options: &[u8],
|
||||
l4_data: &[u8],
|
||||
) -> Result<(), Error> {
|
||||
let IpAddr::V4(destination_v4) = destination_ip else {
|
||||
log::debug!("Destination: {}", destination_ip);
|
||||
todo!();
|
||||
};
|
||||
|
||||
// Lookup route to destination
|
||||
let (iface_id, gateway) = lookup_route(destination_ip).unwrap();
|
||||
let iface = NetworkInterface::get(iface_id).unwrap();
|
||||
|
||||
let Some(IpAddr::V4(gateway_v4)) = gateway else {
|
||||
todo!();
|
||||
};
|
||||
let Some(IpAddr::V4(source_v4)) = *iface.address.read() else {
|
||||
todo!();
|
||||
};
|
||||
|
||||
// Lookup gateway MAC
|
||||
let gateway_mac = arp::lookup(iface.id, IpAddr::V4(gateway_v4)).unwrap();
|
||||
|
||||
let l2_offset = iface.device.packet_prefix_size();
|
||||
let l3_offset = l2_offset + size_of::<EthernetFrame>();
|
||||
let l4_offset = l3_offset + size_of::<Ipv4Frame>();
|
||||
let l4_data_offset = l4_offset + size_of::<L4>();
|
||||
|
||||
let mut packet = PageBox::new_slice(0, l4_data_offset + l4_data.len())?;
|
||||
|
||||
let l2_frame: &mut EthernetFrame = bytemuck::from_bytes_mut(&mut packet[l2_offset..l3_offset]);
|
||||
|
||||
l2_frame.source_mac = iface.mac;
|
||||
l2_frame.destination_mac = gateway_mac;
|
||||
l2_frame.ethertype = EtherType::IPV4.to_network_order();
|
||||
|
||||
let l3_frame: &mut Ipv4Frame = bytemuck::from_bytes_mut(&mut packet[l3_offset..l4_offset]);
|
||||
|
||||
l3_frame.source_address = u32::to_network_order(u32::from(source_v4));
|
||||
l3_frame.destination_address = u32::to_network_order(u32::from(destination_v4));
|
||||
l3_frame.protocol = protocol;
|
||||
l3_frame.version_length = 0x45;
|
||||
l3_frame.dscp_flags = 0;
|
||||
l3_frame.total_length = u16::to_network_order(
|
||||
(size_of::<Ipv4Frame>() + size_of::<L4>() + l4_data.len())
|
||||
.try_into()
|
||||
.unwrap(),
|
||||
);
|
||||
// Disable fragmentation
|
||||
l3_frame.flags_frag = u16::to_network_order(0x4000);
|
||||
l3_frame.id = u16::to_network_order(0);
|
||||
l3_frame.header_checksum = u16::to_network_order(0);
|
||||
l3_frame.ttl = 64;
|
||||
|
||||
let l3_frame_bytes = &packet[l3_offset..l4_offset];
|
||||
let mut ip_checksum = InetChecksum::new();
|
||||
ip_checksum.add_bytes(l3_frame_bytes, true);
|
||||
let ip_checksum = ip_checksum.finish();
|
||||
|
||||
let l3_frame: &mut Ipv4Frame = bytemuck::from_bytes_mut(&mut packet[l3_offset..l4_offset]);
|
||||
l3_frame.header_checksum = u16::to_network_order(ip_checksum);
|
||||
|
||||
packet[l4_offset..l4_data_offset].copy_from_slice(bytemuck::bytes_of(l4_frame));
|
||||
packet[l4_data_offset..l4_data_offset + l4_options.len()].copy_from_slice(l4_options);
|
||||
packet[l4_data_offset + l4_options.len()..].copy_from_slice(l4_data);
|
||||
|
||||
iface.device.transmit(packet)
|
||||
}
|
104
driver/net/core/src/l4/icmp.rs
Normal file
104
driver/net/core/src/l4/icmp.rs
Normal file
@ -0,0 +1,104 @@
|
||||
use core::{
|
||||
mem::size_of,
|
||||
net::{IpAddr, Ipv4Addr},
|
||||
};
|
||||
|
||||
use bytemuck::{Pod, Zeroable};
|
||||
use yggdrasil_abi::error::Error;
|
||||
|
||||
use crate::{
|
||||
l3::{self, ip::Protocol},
|
||||
types::{InetChecksum, NetValue, Value},
|
||||
L3Packet,
|
||||
};
|
||||
|
||||
#[derive(Clone, Copy, Debug, Pod, Zeroable)]
|
||||
#[repr(C)]
|
||||
pub struct IcmpV4Frame {
|
||||
ty: u8,
|
||||
code: u8,
|
||||
checksum: Value<u16>,
|
||||
rest: Value<u32>,
|
||||
}
|
||||
|
||||
fn send_v4_reply(
|
||||
destination_ip: Ipv4Addr,
|
||||
icmp_frame: &IcmpV4Frame,
|
||||
icmp_data: &[u8],
|
||||
) -> Result<(), Error> {
|
||||
let mut reply_frame = IcmpV4Frame {
|
||||
ty: 0,
|
||||
code: 0,
|
||||
checksum: u16::to_network_order(0),
|
||||
rest: icmp_frame.rest,
|
||||
};
|
||||
|
||||
if icmp_data.len() % 2 != 0 {
|
||||
todo!();
|
||||
}
|
||||
|
||||
let l4_bytes = bytemuck::bytes_of(&reply_frame);
|
||||
let mut checksum = InetChecksum::new();
|
||||
checksum.add_bytes(l4_bytes, true);
|
||||
checksum.add_bytes(icmp_data, true);
|
||||
|
||||
reply_frame.checksum = checksum.finish().to_network_order();
|
||||
|
||||
l3::send_l4_ip(
|
||||
IpAddr::V4(destination_ip),
|
||||
Protocol::ICMP,
|
||||
&reply_frame,
|
||||
icmp_data,
|
||||
)
|
||||
}
|
||||
|
||||
fn handle_v4(source_address: Ipv4Addr, l3_packet: L3Packet) -> Result<(), Error> {
|
||||
if l3_packet.data_length < size_of::<IcmpV4Frame>() {
|
||||
log::debug!("Truncated ICMPv4 packet");
|
||||
return Err(Error::MissingData);
|
||||
}
|
||||
|
||||
if l3_packet.data_length - size_of::<IcmpV4Frame>() > 576 {
|
||||
log::debug!("ICMPv4 packet too large");
|
||||
return Err(Error::MissingData);
|
||||
}
|
||||
|
||||
let l3_data = l3_packet.l3_data();
|
||||
let icmp_frame: &IcmpV4Frame = bytemuck::from_bytes(&l3_data[..size_of::<IcmpV4Frame>()]);
|
||||
let icmp_data = &l3_data[size_of::<IcmpV4Frame>()..l3_packet.data_length];
|
||||
|
||||
match (icmp_frame.ty, icmp_frame.code) {
|
||||
(8, 0) => send_v4_reply(source_address, icmp_frame, icmp_data),
|
||||
_ => {
|
||||
log::debug!(
|
||||
"Ignoring unknown ICMPv4 type:code: {}:{}",
|
||||
icmp_frame.ty,
|
||||
icmp_frame.code
|
||||
);
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn handle(l3_packet: L3Packet) -> Result<(), Error> {
|
||||
match l3_packet.source_address {
|
||||
IpAddr::V4(v4) => handle_v4(v4, l3_packet),
|
||||
IpAddr::V6(_) => todo!(),
|
||||
}
|
||||
}
|
||||
|
||||
// fn send_icmp_v4_reply(
|
||||
// l3_frame: &Ipv4Frame,
|
||||
// icmp_frame: &IcmpV4Frame,
|
||||
// icmp_data: &[u8],
|
||||
// ) -> Result<(), Error> {
|
||||
//
|
||||
// l3::send_l4_ip(l3_frame.source_ip(), Protocol::ICMP, &l4_frame, icmp_data)
|
||||
// }
|
||||
//
|
||||
// pub fn handle_icmp_v4_packet(
|
||||
// interface: Arc<NetworkInterface>,
|
||||
// l3_frame: &Ipv4Frame,
|
||||
// l3_data: &[u8],
|
||||
// ) {
|
||||
// }
|
2
driver/net/core/src/l4/mod.rs
Normal file
2
driver/net/core/src/l4/mod.rs
Normal file
@ -0,0 +1,2 @@
|
||||
pub mod icmp;
|
||||
pub mod udp;
|
79
driver/net/core/src/l4/udp.rs
Normal file
79
driver/net/core/src/l4/udp.rs
Normal file
@ -0,0 +1,79 @@
|
||||
use core::{
|
||||
mem::size_of,
|
||||
net::{IpAddr, SocketAddr},
|
||||
};
|
||||
|
||||
use bytemuck::{Pod, Zeroable};
|
||||
use yggdrasil_abi::error::Error;
|
||||
|
||||
use crate::{
|
||||
l3::{self, ip::Protocol, IpFrame},
|
||||
socket::{self, UdpSocket},
|
||||
types::{NetValue, Value},
|
||||
L3Packet,
|
||||
};
|
||||
|
||||
#[derive(Clone, Copy, Pod, Zeroable)]
|
||||
#[repr(C)]
|
||||
pub struct UdpFrame {
|
||||
pub source_port: Value<u16>,
|
||||
pub destination_port: Value<u16>,
|
||||
pub length: Value<u16>,
|
||||
pub checksum: Value<u16>,
|
||||
}
|
||||
|
||||
impl UdpFrame {
|
||||
fn data_length(&self) -> usize {
|
||||
(u16::from_network_order(self.length) as usize)
|
||||
.checked_sub(0)
|
||||
.unwrap_or(0)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn send(
|
||||
source_port: u16,
|
||||
destination_ip: IpAddr,
|
||||
destination_port: u16,
|
||||
data: &[u8],
|
||||
) -> Result<(), Error> {
|
||||
let length: u16 = (data.len() + size_of::<UdpFrame>()).try_into().unwrap();
|
||||
let udp_frame = UdpFrame {
|
||||
source_port: source_port.to_network_order(),
|
||||
destination_port: destination_port.to_network_order(),
|
||||
length: length.to_network_order(),
|
||||
checksum: 0u16.to_network_order(),
|
||||
};
|
||||
|
||||
l3::send_l4_ip(destination_ip, Protocol::UDP, &udp_frame, data)
|
||||
}
|
||||
|
||||
pub fn handle(l3_packet: L3Packet) -> Result<(), Error> {
|
||||
if l3_packet.data_length < size_of::<UdpFrame>() {
|
||||
log::warn!("Truncated UDP frame received");
|
||||
return Err(Error::MissingData);
|
||||
}
|
||||
|
||||
let l3_data = l3_packet.l3_data();
|
||||
|
||||
let udp_frame: &UdpFrame = bytemuck::from_bytes(&l3_data[..size_of::<UdpFrame>()]);
|
||||
let data_size = core::cmp::min(
|
||||
udp_frame.data_length(),
|
||||
l3_packet.data_length - size_of::<UdpFrame>(),
|
||||
);
|
||||
let udp_data = &l3_data[size_of::<UdpFrame>()..data_size + size_of::<UdpFrame>()];
|
||||
|
||||
let source = SocketAddr::new(
|
||||
l3_packet.source_address,
|
||||
u16::from_network_order(udp_frame.source_port),
|
||||
);
|
||||
let destination = SocketAddr::new(
|
||||
l3_packet.destination_address,
|
||||
u16::from_network_order(udp_frame.destination_port),
|
||||
);
|
||||
|
||||
if let Some(socket) = UdpSocket::get(&destination) {
|
||||
socket.packet_received(source, udp_data);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
139
driver/net/core/src/lib.rs
Normal file
139
driver/net/core/src/lib.rs
Normal file
@ -0,0 +1,139 @@
|
||||
#![feature(map_try_insert)]
|
||||
#![no_std]
|
||||
|
||||
extern crate alloc;
|
||||
|
||||
use core::{
|
||||
fmt,
|
||||
future::Future,
|
||||
mem::size_of,
|
||||
net::{IpAddr, Ipv4Addr, SocketAddr, SocketAddrV4},
|
||||
pin::Pin,
|
||||
str::FromStr,
|
||||
sync::atomic::{AtomicU32, Ordering},
|
||||
task::{Context, Poll},
|
||||
};
|
||||
|
||||
use alloc::{collections::BTreeMap, format, string::String, vec::Vec};
|
||||
use ethernet::{EtherType, EthernetFrame, L2Packet};
|
||||
use interface::{NetworkDevice, NetworkInterface};
|
||||
use kernel_fs::devfs;
|
||||
use kernel_util::{
|
||||
mem::PageBox,
|
||||
runtime::{self, QueueWaker},
|
||||
sync::{mutex::Mutex, spin_rwlock::IrqSafeRwLock, IrqSafeSpinlock},
|
||||
util::ring::RingBuffer,
|
||||
};
|
||||
use l3::{ip::Protocol, L3Packet, Route};
|
||||
use queue::Queue;
|
||||
use socket::UdpSocket;
|
||||
use types::{MacAddress, NetValue, SubnetAddress, Value};
|
||||
use vfs::{impls::FnValueNode, PacketSocket};
|
||||
use yggdrasil_abi::error::Error;
|
||||
|
||||
use crate::types::SubnetV4Address;
|
||||
|
||||
pub mod ethernet;
|
||||
pub mod l3;
|
||||
pub mod l4;
|
||||
|
||||
pub mod socket;
|
||||
|
||||
pub mod interface;
|
||||
pub mod queue;
|
||||
pub mod types;
|
||||
|
||||
pub use interface::register_interface;
|
||||
|
||||
pub struct Packet {
|
||||
// TODO info about "received" interface
|
||||
buffer: PageBox<[u8]>,
|
||||
offset: usize,
|
||||
iface: u32,
|
||||
}
|
||||
|
||||
impl Packet {
|
||||
#[inline]
|
||||
pub fn new(buffer: PageBox<[u8]>, offset: usize, iface: u32) -> Self {
|
||||
Self {
|
||||
buffer,
|
||||
offset,
|
||||
iface,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static PACKET_QUEUE: Queue<Packet> = Queue::new();
|
||||
static ACCEPT_QUEUE: Queue<L3Packet> = Queue::new();
|
||||
|
||||
#[inline]
|
||||
pub fn receive_packet(packet: Packet) -> Result<(), Error> {
|
||||
PACKET_QUEUE.receive_packet(packet)
|
||||
}
|
||||
|
||||
pub fn start_network_tasks() -> Result<(), Error> {
|
||||
devfs::add_network_config(
|
||||
"routes",
|
||||
FnValueNode::new(
|
||||
|| {
|
||||
let mut output = String::new();
|
||||
l3::list_routes(|route| {
|
||||
output.push_str(&format!("{}\n", route));
|
||||
});
|
||||
|
||||
Ok(output)
|
||||
},
|
||||
|_value| Err(Error::NotImplemented),
|
||||
),
|
||||
);
|
||||
|
||||
runtime::spawn(l2_packet_handler_worker())?;
|
||||
for _ in 0..1 {
|
||||
runtime::spawn(l3_accept_worker())?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn l2_packet_handler_worker() {
|
||||
loop {
|
||||
let packet = PACKET_QUEUE.wait().await;
|
||||
|
||||
let eth_frame: &EthernetFrame = bytemuck::from_bytes(
|
||||
&packet.buffer[packet.offset..packet.offset + size_of::<EthernetFrame>()],
|
||||
);
|
||||
|
||||
let l2_packet = L2Packet {
|
||||
interface_id: packet.iface,
|
||||
|
||||
source_address: eth_frame.source_mac,
|
||||
destination_address: eth_frame.destination_mac,
|
||||
|
||||
l2_offset: packet.offset,
|
||||
l3_offset: packet.offset + size_of::<EthernetFrame>(),
|
||||
|
||||
data: packet.buffer,
|
||||
};
|
||||
|
||||
ethernet::handle(l2_packet);
|
||||
}
|
||||
}
|
||||
|
||||
async fn l3_accept_worker() {
|
||||
loop {
|
||||
let l3_packet = ACCEPT_QUEUE.wait().await;
|
||||
|
||||
log::debug!(
|
||||
"INPUT {:?} {}:{:?} -> {}:{:?}: ACCEPT",
|
||||
l3_packet.protocol,
|
||||
l3_packet.source_address,
|
||||
l3_packet.source_port,
|
||||
l3_packet.destination_address,
|
||||
l3_packet.destination_port
|
||||
);
|
||||
|
||||
if let Err(error) = l3::handle_accepted(l3_packet) {
|
||||
log::error!("L3 handle error: {:?}", error);
|
||||
}
|
||||
}
|
||||
}
|
53
driver/net/core/src/queue.rs
Normal file
53
driver/net/core/src/queue.rs
Normal file
@ -0,0 +1,53 @@
|
||||
use core::{
|
||||
future::Future,
|
||||
pin::Pin,
|
||||
task::{Context, Poll},
|
||||
};
|
||||
|
||||
use kernel_util::{runtime::QueueWaker, sync::IrqSafeSpinlock, util::ring::RingBuffer};
|
||||
use yggdrasil_abi::error::Error;
|
||||
|
||||
pub struct Queue<T> {
|
||||
queue: IrqSafeSpinlock<RingBuffer<T>>,
|
||||
notify: QueueWaker,
|
||||
}
|
||||
|
||||
impl<T> Queue<T> {
|
||||
pub const fn new() -> Self {
|
||||
Self {
|
||||
queue: IrqSafeSpinlock::new(RingBuffer::with_capacity(1024)),
|
||||
notify: QueueWaker::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn receive_packet(&self, packet: T) -> Result<(), Error> {
|
||||
self.queue.lock().write(packet);
|
||||
self.notify.wake_one();
|
||||
// TODO notify of dropped packets
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn wait(&self) -> impl Future<Output = T> + '_ {
|
||||
struct F<'f, T> {
|
||||
queue: &'f Queue<T>,
|
||||
}
|
||||
|
||||
impl<'f, T> Future for F<'f, T> {
|
||||
type Output = T;
|
||||
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
self.queue.notify.register(cx.waker());
|
||||
let mut lock = self.queue.queue.lock();
|
||||
|
||||
if lock.is_readable() {
|
||||
self.queue.notify.remove(cx.waker());
|
||||
Poll::Ready(unsafe { lock.read_single_unchecked() })
|
||||
} else {
|
||||
Poll::Pending
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
F { queue: self }
|
||||
}
|
||||
}
|
198
driver/net/core/src/socket.rs
Normal file
198
driver/net/core/src/socket.rs
Normal file
@ -0,0 +1,198 @@
|
||||
use core::{
|
||||
future::Future,
|
||||
net::{IpAddr, Ipv4Addr, SocketAddr, SocketAddrV4},
|
||||
pin::Pin,
|
||||
sync::atomic::{AtomicBool, Ordering},
|
||||
task::{Context, Poll},
|
||||
};
|
||||
|
||||
use alloc::{collections::BTreeMap, sync::Arc, vec::Vec};
|
||||
use kernel_util::{
|
||||
block,
|
||||
runtime::QueueWaker,
|
||||
sync::{
|
||||
mutex::{Mutex, MutexGuard},
|
||||
spin_rwlock::{IrqSafeRwLock, IrqSafeRwLockReadGuard},
|
||||
IrqSafeSpinlock, LockMethod,
|
||||
},
|
||||
util::ring::RingBuffer,
|
||||
};
|
||||
use vfs::{FileReadiness, PacketSocket, Socket};
|
||||
use yggdrasil_abi::error::Error;
|
||||
|
||||
use crate::{l4, queue::Queue};
|
||||
|
||||
pub struct UdpSocket {
|
||||
local: SocketAddr,
|
||||
remote: Option<SocketAddr>,
|
||||
// TODO just place packets here for one less copy?
|
||||
receive_queue: Mutex<RingBuffer<(SocketAddr, Vec<u8>)>>,
|
||||
receive_notify: QueueWaker,
|
||||
}
|
||||
|
||||
pub struct SocketTable<T: Socket> {
|
||||
inner: BTreeMap<SocketAddr, Arc<T>>,
|
||||
}
|
||||
|
||||
impl<T: Socket> SocketTable<T> {
|
||||
pub const fn new() -> Self {
|
||||
Self {
|
||||
inner: BTreeMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn insert(&mut self, address: SocketAddr, socket: Arc<T>) -> Result<(), Error> {
|
||||
match self.inner.try_insert(address, socket) {
|
||||
Ok(_) => Ok(()),
|
||||
Err(_) => return Err(Error::AlreadyExists),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn remove(&mut self, local: SocketAddr) -> Result<(), Error> {
|
||||
match self.inner.remove(&local) {
|
||||
Some(_) => Ok(()),
|
||||
None => Err(Error::DoesNotExist),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_exact(&self, local: &SocketAddr) -> Option<Arc<T>> {
|
||||
self.inner.get(local).cloned()
|
||||
}
|
||||
|
||||
pub fn get(&self, local: &SocketAddr) -> Option<Arc<T>> {
|
||||
if let Some(socket) = self.inner.get(local) {
|
||||
return Some(socket.clone());
|
||||
}
|
||||
|
||||
match local {
|
||||
SocketAddr::V4(v4) => {
|
||||
let unspec_v4 = SocketAddrV4::new(Ipv4Addr::UNSPECIFIED, local.port());
|
||||
self.inner.get(&SocketAddr::V4(unspec_v4)).cloned()
|
||||
}
|
||||
SocketAddr::V6(_) => todo!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static UDP_SOCKETS: IrqSafeRwLock<SocketTable<UdpSocket>> = IrqSafeRwLock::new(SocketTable::new());
|
||||
|
||||
impl UdpSocket {
|
||||
pub fn bind(address: SocketAddr) -> Result<Arc<UdpSocket>, Error> {
|
||||
let mut sockets = UDP_SOCKETS.write();
|
||||
|
||||
if sockets.get(&address).is_some() {
|
||||
// TODO use network-specific error
|
||||
return Err(Error::AlreadyExists);
|
||||
}
|
||||
|
||||
let socket = Arc::new(Self {
|
||||
local: address,
|
||||
remote: None,
|
||||
receive_queue: Mutex::new(RingBuffer::try_with_capacity(128)?),
|
||||
receive_notify: QueueWaker::new(),
|
||||
});
|
||||
|
||||
sockets.insert(address, socket.clone());
|
||||
|
||||
log::debug!("UDP socket opened: {}", address);
|
||||
|
||||
Ok(socket)
|
||||
}
|
||||
|
||||
pub fn connect(address: SocketAddr) -> Result<Arc<UdpSocket>, Error> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
pub fn get(local: &SocketAddr) -> Option<Arc<UdpSocket>> {
|
||||
UDP_SOCKETS.read().get(local)
|
||||
}
|
||||
|
||||
fn poll_receive(
|
||||
&self,
|
||||
cx: &mut Context<'_>,
|
||||
) -> Poll<Result<MutexGuard<RingBuffer<(SocketAddr, Vec<u8>)>>, Error>> {
|
||||
self.receive_notify.register(cx.waker());
|
||||
let mut lock = self.receive_queue.lock()?;
|
||||
if lock.is_readable() {
|
||||
self.receive_notify.remove(cx.waker());
|
||||
Poll::Ready(Ok(lock))
|
||||
} else {
|
||||
Poll::Pending
|
||||
}
|
||||
}
|
||||
|
||||
pub fn receive_raw<'a>(
|
||||
&'a self,
|
||||
) -> impl Future<Output = Result<(SocketAddr, Vec<u8>), Error>> + 'a {
|
||||
struct F<'f> {
|
||||
socket: &'f UdpSocket,
|
||||
}
|
||||
|
||||
impl<'f> Future for F<'f> {
|
||||
type Output = Result<(SocketAddr, Vec<u8>), Error>;
|
||||
|
||||
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
match self.socket.poll_receive(cx)? {
|
||||
Poll::Ready(mut lock) => {
|
||||
let (source, data) = unsafe { lock.read_single_unchecked() };
|
||||
Poll::Ready(Ok((source, data)))
|
||||
}
|
||||
Poll::Pending => Poll::Pending,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
F { socket: self }
|
||||
}
|
||||
|
||||
pub fn packet_received(&self, source: SocketAddr, data: &[u8]) -> Result<(), Error> {
|
||||
let mut lock = self.receive_queue.lock()?;
|
||||
lock.write((source, Vec::from(data)));
|
||||
self.receive_notify.wake_one();
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl FileReadiness for UdpSocket {
|
||||
fn poll_read(&self, cx: &mut Context<'_>) -> Poll<Result<(), Error>> {
|
||||
self.poll_receive(cx).map_ok(|_| ())
|
||||
}
|
||||
}
|
||||
|
||||
impl PacketSocket for UdpSocket {
|
||||
fn send(&self, destination: SocketAddr, data: &[u8]) -> Result<usize, Error> {
|
||||
// TODO check that destnation family matches self family
|
||||
l4::udp::send(
|
||||
self.local.port(),
|
||||
destination.ip(),
|
||||
destination.port(),
|
||||
data,
|
||||
)?;
|
||||
|
||||
Ok(data.len())
|
||||
}
|
||||
|
||||
fn receive(&self, buffer: &mut [u8]) -> Result<(SocketAddr, usize), Error> {
|
||||
let (source, data) = block!(self.receive_raw().await)??;
|
||||
if data.len() > buffer.len() {
|
||||
todo!()
|
||||
}
|
||||
buffer[..data.len()].copy_from_slice(&data);
|
||||
Ok((source, data.len()))
|
||||
}
|
||||
}
|
||||
|
||||
impl Socket for UdpSocket {
|
||||
fn local_address(&self) -> SocketAddr {
|
||||
self.local
|
||||
}
|
||||
|
||||
fn remote_address(&self) -> Option<SocketAddr> {
|
||||
self.remote
|
||||
}
|
||||
|
||||
fn close(&self) -> Result<(), Error> {
|
||||
log::debug!("UDP socket closed: {}", self.local);
|
||||
UDP_SOCKETS.write().remove(self.local)
|
||||
}
|
||||
}
|
225
driver/net/core/src/types.rs
Normal file
225
driver/net/core/src/types.rs
Normal file
@ -0,0 +1,225 @@
|
||||
use core::{
|
||||
fmt,
|
||||
net::{IpAddr, Ipv4Addr},
|
||||
str::FromStr,
|
||||
};
|
||||
|
||||
use bytemuck::{Pod, Zeroable};
|
||||
use yggdrasil_abi::error::Error;
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! wrap_value {
|
||||
($vis:vis struct $name:ident($ty_vis:vis $ty:ty)) => {
|
||||
#[derive(Clone, Copy, Debug, bytemuck::Pod, bytemuck::Zeroable)]
|
||||
$vis struct $name($ty_vis $ty);
|
||||
|
||||
impl $crate::types::NetValue for $name {
|
||||
fn from_network_order(value: $crate::types::Value<Self>) -> Self {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn to_network_order(self) -> $crate::types::Value<Self> {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub trait NetValue: Copy + Eq + Pod + Zeroable {
|
||||
fn from_network_order(value: Value<Self>) -> Self;
|
||||
fn to_network_order(self) -> Value<Self>;
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Pod, Zeroable)]
|
||||
#[repr(transparent)]
|
||||
pub struct Value<T: NetValue>(pub T);
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Pod, Zeroable)]
|
||||
#[repr(transparent)]
|
||||
pub struct MacAddress([u8; 6]);
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
||||
pub struct SubnetV4Address {
|
||||
pub root: Ipv4Addr,
|
||||
pub mask_bits: u8,
|
||||
pub mask: u32,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
||||
pub enum SubnetAddress {
|
||||
V4(SubnetV4Address),
|
||||
}
|
||||
|
||||
impl<T: NetValue + fmt::Debug> fmt::Debug for Value<T> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let value = T::from_network_order(*self);
|
||||
fmt::Debug::fmt(&value, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl NetValue for u16 {
|
||||
fn from_network_order(value: Value<Self>) -> Self {
|
||||
Self::from_be(value.0)
|
||||
}
|
||||
|
||||
fn to_network_order(self) -> Value<Self> {
|
||||
Value(self.to_be())
|
||||
}
|
||||
}
|
||||
|
||||
impl NetValue for u32 {
|
||||
fn from_network_order(value: Value<Self>) -> Self {
|
||||
Self::from_be(value.0)
|
||||
}
|
||||
|
||||
fn to_network_order(self) -> Value<Self> {
|
||||
Value(self.to_be())
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for MacAddress {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
use fmt::Write;
|
||||
|
||||
for (i, &v) in self.0.iter().enumerate() {
|
||||
if i != 0 {
|
||||
write!(f, ":")?;
|
||||
}
|
||||
write!(f, "{:02X}", v)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<MacAddress> for [u8; 6] {
|
||||
fn from(value: MacAddress) -> Self {
|
||||
value.0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<[u8; 6]> for MacAddress {
|
||||
fn from(value: [u8; 6]) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl SubnetV4Address {
|
||||
pub const UNSPECIFIED: Self = Self {
|
||||
root: Ipv4Addr::new(0, 0, 0, 0),
|
||||
mask: 0,
|
||||
mask_bits: 0,
|
||||
};
|
||||
|
||||
pub fn from_address_mask(root: Ipv4Addr, mask_bits: u8) -> Self {
|
||||
let root = u32::from(root);
|
||||
let mask = Self::make_mask(mask_bits);
|
||||
// Clear masked bits
|
||||
let root = root & mask;
|
||||
|
||||
Self {
|
||||
root: Ipv4Addr::from(root),
|
||||
mask_bits,
|
||||
mask,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn contains(&self, address: &Ipv4Addr) -> bool {
|
||||
let root = u32::from(self.root);
|
||||
let address = u32::from(*address);
|
||||
|
||||
(address & self.mask) == root
|
||||
}
|
||||
|
||||
fn make_mask(bits: u8) -> u32 {
|
||||
((1 << bits) - 1) << (32 - bits)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for SubnetV4Address {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}/{}", self.root, self.mask_bits)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for SubnetV4Address {
|
||||
type Err = Error;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let (addr, mask) = s.split_once('/').ok_or(Error::InvalidArgument)?;
|
||||
let addr = Ipv4Addr::from_str(addr).map_err(|_| Error::InvalidArgument)?;
|
||||
let mask = u8::from_str(mask).map_err(|_| Error::InvalidArgument)?;
|
||||
|
||||
Ok(Self::from_address_mask(addr, mask))
|
||||
}
|
||||
}
|
||||
|
||||
impl SubnetAddress {
|
||||
pub const UNSPECIFIED_V4: Self = Self::V4(SubnetV4Address::UNSPECIFIED);
|
||||
|
||||
pub fn contains(&self, address: &IpAddr) -> bool {
|
||||
match (self, address) {
|
||||
(Self::V4(subnet), IpAddr::V4(address)) => subnet.contains(address),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_v4(&self) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
impl From<SubnetV4Address> for SubnetAddress {
|
||||
fn from(value: SubnetV4Address) -> Self {
|
||||
Self::V4(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for SubnetAddress {
|
||||
type Err = Error;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
// TODO v6
|
||||
let v4 = SubnetV4Address::from_str(s)?;
|
||||
Ok(Self::from(v4))
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for SubnetAddress {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::V4(v4) => fmt::Display::fmt(v4, f),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct InetChecksum {
|
||||
state: u32,
|
||||
}
|
||||
|
||||
impl InetChecksum {
|
||||
pub fn new() -> Self {
|
||||
Self { state: 0 }
|
||||
}
|
||||
|
||||
pub fn add_bytes(&mut self, bytes: &[u8], reorder: bool) {
|
||||
let len = bytes.len();
|
||||
for i in 0..len / 2 {
|
||||
let word = if reorder {
|
||||
((bytes[i * 2] as u16) << 8) | (bytes[i * 2 + 1] as u16)
|
||||
} else {
|
||||
(bytes[i * 2] as u16) | ((bytes[i * 2 + 1] as u16) << 8)
|
||||
};
|
||||
self.state = self.state.wrapping_add(word as u32);
|
||||
}
|
||||
if len % 2 != 0 {
|
||||
self.state += self.state.wrapping_add(bytes[len - 1] as u32);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn finish(self) -> u16 {
|
||||
let sum = (self.state >> 16) + (self.state & 0xFFFF);
|
||||
|
||||
(!(sum & 0xFFFF)) as u16
|
||||
}
|
||||
}
|
21
driver/virtio/core/Cargo.toml
Normal file
21
driver/virtio/core/Cargo.toml
Normal file
@ -0,0 +1,21 @@
|
||||
[package]
|
||||
name = "ygg_driver_virtio_core"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
yggdrasil-abi = { git = "https://git.alnyan.me/yggdrasil/yggdrasil-abi.git" }
|
||||
kernel-util = { path = "../../../lib/kernel-util" }
|
||||
device-api = { path = "../../../lib/device-api", features = ["derive"] }
|
||||
|
||||
ygg_driver_pci = { path = "../../bus/pci", optional = true }
|
||||
|
||||
log = "0.4.20"
|
||||
bitflags = "2.4.2"
|
||||
tock-registers = "0.8.1"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
pci = ["ygg_driver_pci"]
|
20
driver/virtio/core/src/error.rs
Normal file
20
driver/virtio/core/src/error.rs
Normal file
@ -0,0 +1,20 @@
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
OsError(yggdrasil_abi::error::Error),
|
||||
InvalidPciConfiguration,
|
||||
NoCommonConfigCapability,
|
||||
NoNotifyConfigCapability,
|
||||
NoDeviceConfigCapability,
|
||||
QueueTooLarge,
|
||||
InvalidQueueSize,
|
||||
EmptyTransaction,
|
||||
QueueFull,
|
||||
QueueEmpty,
|
||||
WrongToken,
|
||||
}
|
||||
|
||||
impl From<yggdrasil_abi::error::Error> for Error {
|
||||
fn from(value: yggdrasil_abi::error::Error) -> Self {
|
||||
Self::OsError(value)
|
||||
}
|
||||
}
|
54
driver/virtio/core/src/lib.rs
Normal file
54
driver/virtio/core/src/lib.rs
Normal file
@ -0,0 +1,54 @@
|
||||
#![no_std]
|
||||
|
||||
extern crate alloc;
|
||||
|
||||
pub mod error;
|
||||
pub mod queue;
|
||||
pub mod transport;
|
||||
|
||||
use bitflags::bitflags;
|
||||
use tock_registers::{
|
||||
interfaces::{Readable, Writeable},
|
||||
register_structs,
|
||||
registers::{ReadOnly, ReadWrite, WriteOnly},
|
||||
};
|
||||
|
||||
register_structs! {
|
||||
pub CommonConfiguration {
|
||||
(0x00 => device_feature_select: ReadWrite<u32>),
|
||||
(0x04 => device_feature: ReadOnly<u32>),
|
||||
(0x08 => driver_feature_select: ReadWrite<u32>),
|
||||
(0x0C => driver_feature: ReadWrite<u32>),
|
||||
(0x10 => msix_config: ReadWrite<u16>),
|
||||
(0x12 => num_queues: ReadOnly<u16>),
|
||||
(0x14 => device_status: ReadWrite<u8>),
|
||||
(0x15 => config_generation: ReadOnly<u8>),
|
||||
|
||||
(0x16 => queue_select: ReadWrite<u16>),
|
||||
(0x18 => queue_size: ReadWrite<u16>),
|
||||
(0x1A => queue_msix_vector: ReadWrite<u16>),
|
||||
(0x1C => queue_enable: ReadWrite<u16>),
|
||||
(0x1E => queue_notify_off: ReadWrite<u16>),
|
||||
(0x20 => queue_desc: ReadWrite<u64>),
|
||||
(0x28 => queue_driver: ReadWrite<u64>),
|
||||
(0x30 => queue_device: ReadWrite<u64>),
|
||||
|
||||
(0x38 => @END),
|
||||
}
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct DeviceStatus: u8 {
|
||||
const ACKNOWLEDGE = 1 << 0;
|
||||
const DRIVER = 1 << 1;
|
||||
const DRIVER_OK = 1 << 2;
|
||||
const FEATURES_OK = 1 << 3;
|
||||
const DEVICE_NEEDS_RESET = 1 << 6;
|
||||
const FAILED = 1 << 7;
|
||||
}
|
||||
}
|
||||
|
||||
impl DeviceStatus {
|
||||
pub const RESET_VALUE: Self = Self::empty();
|
||||
}
|
344
driver/virtio/core/src/queue.rs
Normal file
344
driver/virtio/core/src/queue.rs
Normal file
@ -0,0 +1,344 @@
|
||||
//! VirtIO queue implementation.
|
||||
//!
|
||||
//! # Note
|
||||
//!
|
||||
//! The code is poorly borrowed from `virtio-drivers` crate. I want to rewrite it properly myself.
|
||||
use core::{
|
||||
mem::MaybeUninit,
|
||||
sync::atomic::{fence, Ordering},
|
||||
};
|
||||
|
||||
use alloc::boxed::Box;
|
||||
use kernel_util::mem::{
|
||||
address::{AsPhysicalAddress, IntoRaw},
|
||||
PageBox,
|
||||
};
|
||||
|
||||
use crate::{error::Error, transport::Transport};
|
||||
|
||||
#[derive(Debug)]
|
||||
#[repr(C)]
|
||||
struct Descriptor {
|
||||
address: u64,
|
||||
len: u32,
|
||||
flags: u16,
|
||||
next: u16,
|
||||
}
|
||||
|
||||
// Layout:
|
||||
// {
|
||||
// flags: u16,
|
||||
// idx: u16,
|
||||
// ring: [u16; QUEUE_SIZE],
|
||||
// used_event: u16
|
||||
// }
|
||||
struct AvailableRing {
|
||||
data: PageBox<[MaybeUninit<u16>]>,
|
||||
}
|
||||
|
||||
// Layout:
|
||||
// {
|
||||
// flags: u16,
|
||||
// idx: u16,
|
||||
// ring: [UsedElem; QUEUE_SIZE],
|
||||
// avail_event: u16,
|
||||
// _pad: u16
|
||||
// }
|
||||
struct UsedRing {
|
||||
data: PageBox<[MaybeUninit<u32>]>,
|
||||
|
||||
used_count: usize,
|
||||
}
|
||||
|
||||
pub struct VirtQueue {
|
||||
descriptor_table: PageBox<[MaybeUninit<Descriptor>]>,
|
||||
available: AvailableRing,
|
||||
used: UsedRing,
|
||||
|
||||
capacity: usize,
|
||||
|
||||
queue_index: u16,
|
||||
free_head: u16,
|
||||
|
||||
avail_idx: u16,
|
||||
last_used_idx: u16,
|
||||
|
||||
msix_vector: u16,
|
||||
}
|
||||
|
||||
impl AvailableRing {
|
||||
pub fn with_capacity(capacity: usize) -> Result<Self, Error> {
|
||||
let mut data = PageBox::new_uninit_slice(capacity + 3)?;
|
||||
|
||||
data[1].write(0);
|
||||
|
||||
Ok(Self { data })
|
||||
}
|
||||
|
||||
pub fn set_head(&mut self, slot: u16, head: u16) {
|
||||
self.data[slot as usize + 2].write(head);
|
||||
}
|
||||
|
||||
pub fn set_index(&mut self, index: u16) {
|
||||
self.data[1].write(index);
|
||||
}
|
||||
}
|
||||
|
||||
impl UsedRing {
|
||||
pub fn with_capacity(capacity: usize) -> Result<Self, Error> {
|
||||
let mut data = PageBox::new_uninit_slice(capacity * 2 + 2)?;
|
||||
|
||||
data[0].write(0);
|
||||
|
||||
Ok(Self {
|
||||
data,
|
||||
used_count: 0,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn read_slot(&self, index: u16) -> (u32, u32) {
|
||||
let index = unsafe { self.data[1 + index as usize * 2].assume_init() };
|
||||
let len = unsafe { self.data[2 + index as usize * 2].assume_init() };
|
||||
(index, len)
|
||||
}
|
||||
|
||||
pub fn index(&self) -> u16 {
|
||||
unsafe { (self.data[0].assume_init() >> 16) as u16 }
|
||||
}
|
||||
}
|
||||
|
||||
impl VirtQueue {
|
||||
pub fn with_capacity<T: Transport>(
|
||||
transport: &mut T,
|
||||
index: u16,
|
||||
capacity: usize,
|
||||
msix_vector: Option<u16>,
|
||||
) -> Result<Self, Error> {
|
||||
// TODO check if queue is already set up
|
||||
|
||||
let max_capacity = transport.max_queue_size(index);
|
||||
|
||||
if !capacity.is_power_of_two() || capacity > u16::MAX.into() {
|
||||
return Err(Error::InvalidQueueSize);
|
||||
}
|
||||
|
||||
if capacity > max_capacity as usize {
|
||||
return Err(Error::QueueTooLarge);
|
||||
}
|
||||
|
||||
let descriptor_table = PageBox::new_uninit_slice(capacity)?;
|
||||
let available = AvailableRing::with_capacity(capacity)?;
|
||||
let used = UsedRing::with_capacity(capacity)?;
|
||||
|
||||
transport.set_queue(
|
||||
index,
|
||||
capacity as u16,
|
||||
unsafe { descriptor_table.as_physical_address() },
|
||||
unsafe { available.data.as_physical_address() },
|
||||
unsafe { used.data.as_physical_address() },
|
||||
msix_vector,
|
||||
);
|
||||
|
||||
Ok(Self {
|
||||
descriptor_table,
|
||||
available,
|
||||
used,
|
||||
|
||||
capacity,
|
||||
|
||||
queue_index: index,
|
||||
free_head: 0,
|
||||
|
||||
avail_idx: 0,
|
||||
last_used_idx: 0,
|
||||
|
||||
msix_vector: msix_vector.unwrap_or(0xFFFF),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn capacity(&self) -> usize {
|
||||
self.capacity
|
||||
}
|
||||
|
||||
pub fn with_max_capacity<T: Transport>(
|
||||
transport: &mut T,
|
||||
index: u16,
|
||||
capacity: usize,
|
||||
msix_vector: Option<u16>,
|
||||
) -> Result<Self, Error> {
|
||||
let max_capacity = transport.max_queue_size(index);
|
||||
let capacity = capacity.min(max_capacity as usize);
|
||||
|
||||
Self::with_capacity(transport, index, capacity, msix_vector)
|
||||
}
|
||||
|
||||
pub unsafe fn add<'a, 'b>(
|
||||
&mut self,
|
||||
input: &'a [&'b mut PageBox<[u8]>],
|
||||
output: &'a [&'b PageBox<[u8]>],
|
||||
) -> Result<u16, Error> {
|
||||
if input.is_empty() && output.is_empty() {
|
||||
return Err(Error::EmptyTransaction);
|
||||
}
|
||||
let n_desc = input.len() + output.len();
|
||||
|
||||
if self.used.used_count + 1 > self.capacity || self.used.used_count + n_desc > self.capacity
|
||||
{
|
||||
return Err(Error::QueueFull);
|
||||
}
|
||||
|
||||
let head = self.add_direct(input, output);
|
||||
let avail_slot = self.avail_idx % self.capacity as u16;
|
||||
|
||||
self.available.set_head(avail_slot, head);
|
||||
|
||||
fence(Ordering::SeqCst);
|
||||
|
||||
self.avail_idx = self.avail_idx.wrapping_add(1);
|
||||
self.available.set_index(self.avail_idx);
|
||||
|
||||
fence(Ordering::SeqCst);
|
||||
|
||||
Ok(head)
|
||||
}
|
||||
|
||||
unsafe fn add_direct<'a, 'b>(
|
||||
&mut self,
|
||||
input: &'a [&'b mut PageBox<[u8]>],
|
||||
output: &'a [&'b PageBox<[u8]>],
|
||||
) -> u16 {
|
||||
let head = self.free_head;
|
||||
let mut last = self.free_head;
|
||||
|
||||
for item in input {
|
||||
assert_ne!(item.len(), 0);
|
||||
let desc = &mut self.descriptor_table[usize::from(self.free_head)];
|
||||
let next = (self.free_head + 1) % self.capacity as u16;
|
||||
|
||||
let desc = desc.write(Descriptor {
|
||||
address: item.as_physical_address().into_raw(),
|
||||
len: item.len().try_into().unwrap(),
|
||||
// TODO MAGIC
|
||||
flags: (1 << 0) | (1 << 1),
|
||||
next,
|
||||
});
|
||||
|
||||
last = self.free_head;
|
||||
self.free_head = next;
|
||||
}
|
||||
|
||||
for item in output {
|
||||
assert_ne!(item.len(), 0);
|
||||
let desc = &mut self.descriptor_table[usize::from(self.free_head)];
|
||||
let next = (self.free_head + 1) % self.capacity as u16;
|
||||
|
||||
let desc = desc.write(Descriptor {
|
||||
address: item.as_physical_address().into_raw(),
|
||||
len: item.len().try_into().unwrap(),
|
||||
// TODO
|
||||
flags: (1 << 0),
|
||||
next,
|
||||
});
|
||||
|
||||
last = self.free_head;
|
||||
self.free_head = next;
|
||||
}
|
||||
|
||||
{
|
||||
let last_desc = self.descriptor_table[last as usize].assume_init_mut();
|
||||
|
||||
// TODO
|
||||
last_desc.flags &= !(1 << 0);
|
||||
}
|
||||
|
||||
self.used.used_count += (input.len() + output.len());
|
||||
|
||||
head
|
||||
}
|
||||
|
||||
pub fn add_notify_wait_pop<'a, 'b, T: Transport>(
|
||||
&mut self,
|
||||
input: &'a [&'b mut PageBox<[u8]>],
|
||||
output: &'a [&'b PageBox<[u8]>],
|
||||
transport: &mut T,
|
||||
) -> Result<u32, Error> {
|
||||
let token = unsafe { self.add(input, output) }?;
|
||||
|
||||
transport.notify(self.queue_index);
|
||||
|
||||
while self.is_used_empty() {
|
||||
core::hint::spin_loop();
|
||||
}
|
||||
|
||||
unsafe { self.pop_used(token) }
|
||||
}
|
||||
|
||||
pub fn is_used_empty(&self) -> bool {
|
||||
fence(Ordering::SeqCst);
|
||||
|
||||
self.last_used_idx == self.used.index()
|
||||
}
|
||||
|
||||
pub unsafe fn pop_last_used(&mut self) -> Option<(u16, u32)> {
|
||||
let token = self.peek_used()?;
|
||||
let len = self.pop_used(token).unwrap();
|
||||
|
||||
Some((token, len))
|
||||
}
|
||||
|
||||
pub unsafe fn peek_used(&mut self) -> Option<u16> {
|
||||
if !self.is_used_empty() {
|
||||
let last_used = self.last_used_idx % self.capacity as u16;
|
||||
Some(self.used.read_slot(last_used).0 as u16)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub unsafe fn pop_used(&mut self, token: u16) -> Result<u32, Error> {
|
||||
if self.is_used_empty() {
|
||||
return Err(Error::QueueEmpty);
|
||||
}
|
||||
|
||||
let last_used_slot = self.last_used_idx % self.capacity as u16;
|
||||
let (index, len) = self.used.read_slot(last_used_slot);
|
||||
|
||||
if index != token as u32 {
|
||||
return Err(Error::WrongToken);
|
||||
}
|
||||
|
||||
let freed_count = self.free_descriptor_chain(token);
|
||||
|
||||
self.last_used_idx = self.last_used_idx.wrapping_add(1);
|
||||
|
||||
Ok(len)
|
||||
}
|
||||
|
||||
unsafe fn free_descriptor_chain(&mut self, head: u16) -> usize {
|
||||
let mut current_node = Some(self.descriptor_table[usize::from(head)].assume_init_mut());
|
||||
let mut count = 0;
|
||||
|
||||
while let Some(current) = current_node {
|
||||
assert_ne!(current.len, 0);
|
||||
let next_head = (current.flags & (1 << 0) != 0).then_some(current.next);
|
||||
|
||||
current.address = 0;
|
||||
current.flags = 0;
|
||||
current.next = 0;
|
||||
current.len = 0;
|
||||
|
||||
self.used.used_count -= 1;
|
||||
count += 1;
|
||||
|
||||
current_node =
|
||||
next_head.map(|head| self.descriptor_table[usize::from(head)].assume_init_mut());
|
||||
}
|
||||
|
||||
self.free_head = head;
|
||||
count
|
||||
}
|
||||
|
||||
pub fn msix_vector(&self) -> u16 {
|
||||
self.msix_vector
|
||||
}
|
||||
}
|
92
driver/virtio/core/src/transport/mod.rs
Normal file
92
driver/virtio/core/src/transport/mod.rs
Normal file
@ -0,0 +1,92 @@
|
||||
use core::mem::size_of;
|
||||
|
||||
use kernel_util::mem::{
|
||||
address::{IntoRaw, PhysicalAddress},
|
||||
device::DeviceMemoryIo,
|
||||
};
|
||||
use tock_registers::{
|
||||
interfaces::{Readable, Writeable},
|
||||
registers::{ReadWrite, WriteOnly},
|
||||
};
|
||||
|
||||
use crate::{CommonConfiguration, DeviceStatus};
|
||||
|
||||
pub mod pci;
|
||||
|
||||
pub trait Transport {
|
||||
fn common_cfg(&self) -> &CommonConfiguration;
|
||||
fn notify_cfg(&self) -> &[WriteOnly<u16>];
|
||||
fn notify_off_mul(&self) -> usize;
|
||||
fn supports_msix(&self) -> bool;
|
||||
fn device_cfg(&self) -> Option<&DeviceMemoryIo<[u8]>>;
|
||||
|
||||
fn read_device_features(&mut self) -> u64 {
|
||||
let cfg = self.common_cfg();
|
||||
cfg.device_feature_select.set(0);
|
||||
let low = cfg.device_feature.get();
|
||||
cfg.device_feature_select.set(1);
|
||||
let high = cfg.device_feature.get();
|
||||
|
||||
(low as u64) | ((high as u64) << 32)
|
||||
}
|
||||
|
||||
fn write_driver_features(&mut self, value: u64) {
|
||||
let cfg = self.common_cfg();
|
||||
cfg.driver_feature_select.set(0);
|
||||
cfg.driver_feature.set(value as u32);
|
||||
cfg.driver_feature_select.set(1);
|
||||
cfg.driver_feature.set((value >> 32) as u32);
|
||||
}
|
||||
|
||||
fn read_device_status(&mut self) -> DeviceStatus {
|
||||
let cfg = self.common_cfg();
|
||||
DeviceStatus::from_bits_retain(cfg.device_status.get())
|
||||
}
|
||||
|
||||
fn write_device_status(&mut self, value: DeviceStatus) {
|
||||
let cfg = self.common_cfg();
|
||||
cfg.device_status.set(value.bits());
|
||||
}
|
||||
|
||||
fn max_queue_size(&mut self, queue: u16) -> u32 {
|
||||
let cfg = self.common_cfg();
|
||||
cfg.queue_select.set(queue);
|
||||
cfg.queue_size.get().into()
|
||||
}
|
||||
|
||||
fn set_queue(
|
||||
&mut self,
|
||||
queue: u16,
|
||||
capacity: u16,
|
||||
descriptor_table_phys: PhysicalAddress,
|
||||
available_ring_phys: PhysicalAddress,
|
||||
used_ring_phys: PhysicalAddress,
|
||||
msix_vector: Option<u16>,
|
||||
) {
|
||||
let cfg = self.common_cfg();
|
||||
cfg.queue_select.set(queue);
|
||||
cfg.queue_size.set(capacity);
|
||||
cfg.queue_desc.set(descriptor_table_phys.into_raw());
|
||||
cfg.queue_driver.set(available_ring_phys.into_raw());
|
||||
cfg.queue_device.set(used_ring_phys.into_raw());
|
||||
if self.supports_msix() {
|
||||
cfg.queue_msix_vector.set(msix_vector.unwrap_or(0xFFFF));
|
||||
}
|
||||
cfg.queue_enable.set(1);
|
||||
}
|
||||
|
||||
fn unset_queue(&mut self, queue: u16) {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn notify(&mut self, queue: u16) {
|
||||
let cfg = self.common_cfg();
|
||||
let notify = self.notify_cfg();
|
||||
|
||||
cfg.queue_select.set(queue);
|
||||
let notify_off = cfg.queue_notify_off.get() as usize;
|
||||
let index = (notify_off * self.notify_off_mul()) / size_of::<u16>();
|
||||
|
||||
notify[index].set(queue);
|
||||
}
|
||||
}
|
119
driver/virtio/core/src/transport/pci.rs
Normal file
119
driver/virtio/core/src/transport/pci.rs
Normal file
@ -0,0 +1,119 @@
|
||||
use kernel_util::mem::{
|
||||
address::{FromRaw, PhysicalAddress},
|
||||
device::DeviceMemoryIo,
|
||||
};
|
||||
use tock_registers::registers::WriteOnly;
|
||||
use ygg_driver_pci::{
|
||||
capability::{
|
||||
MsiXCapability, VirtioCapabilityData, VirtioCommonConfigCapability,
|
||||
VirtioDeviceConfigCapability, VirtioNotifyConfigCapability,
|
||||
},
|
||||
PciCommandRegister, PciConfigurationSpace,
|
||||
};
|
||||
|
||||
use crate::{error::Error, CommonConfiguration};
|
||||
|
||||
use super::Transport;
|
||||
|
||||
pub struct PciTransport {
|
||||
common_cfg: DeviceMemoryIo<'static, CommonConfiguration>,
|
||||
device_cfg: DeviceMemoryIo<'static, [u8]>,
|
||||
notify_cfg: DeviceMemoryIo<'static, [WriteOnly<u16>]>,
|
||||
notify_cfg_mul: usize,
|
||||
}
|
||||
|
||||
impl Transport for PciTransport {
|
||||
fn common_cfg(&self) -> &CommonConfiguration {
|
||||
&self.common_cfg
|
||||
}
|
||||
|
||||
fn notify_cfg(&self) -> &[WriteOnly<u16>] {
|
||||
&self.notify_cfg
|
||||
}
|
||||
|
||||
fn notify_off_mul(&self) -> usize {
|
||||
self.notify_cfg_mul
|
||||
}
|
||||
|
||||
fn supports_msix(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn device_cfg(&self) -> Option<&DeviceMemoryIo<[u8]>> {
|
||||
Some(&self.device_cfg)
|
||||
}
|
||||
}
|
||||
|
||||
impl PciTransport {
|
||||
pub fn from_config_space<S: PciConfigurationSpace + ?Sized>(space: &S) -> Result<Self, Error> {
|
||||
// Transitional devices MUST have a PCI Revision ID of 0.
|
||||
// Transitional devices MUST have the PCI Subsystem Device ID
|
||||
// matching the Virtio Device ID, as indicated in section 5.
|
||||
// Transitional devices MUST have the Transitional PCI
|
||||
// Device ID in the range 0x1000 to 0x103f.
|
||||
// TODO check PCI subsystem ID
|
||||
if space.rev_id() != 0 {
|
||||
return Err(Error::InvalidPciConfiguration);
|
||||
}
|
||||
|
||||
let mut cmd = PciCommandRegister::from_bits_retain(space.command());
|
||||
cmd &= !(PciCommandRegister::DISABLE_INTERRUPTS | PciCommandRegister::ENABLE_IO);
|
||||
cmd |= PciCommandRegister::ENABLE_MEMORY | PciCommandRegister::BUS_MASTER;
|
||||
space.set_command(cmd.bits());
|
||||
|
||||
// Extract capabilities
|
||||
|
||||
let common_cfg_cap = space
|
||||
.capability::<VirtioCommonConfigCapability>()
|
||||
.ok_or(Error::NoCommonConfigCapability)?;
|
||||
// TODO this is not mandatory
|
||||
let device_cfg_cap = space
|
||||
.capability::<VirtioDeviceConfigCapability>()
|
||||
.ok_or(Error::NoDeviceConfigCapability)?;
|
||||
let notify_cfg_cap = space
|
||||
.capability::<VirtioNotifyConfigCapability>()
|
||||
.ok_or(Error::NoNotifyConfigCapability)?;
|
||||
|
||||
// TODO MSI/MSI-X
|
||||
|
||||
// Map the regions
|
||||
|
||||
let common_cfg_base = space
|
||||
.bar(common_cfg_cap.bar_index().unwrap())
|
||||
.unwrap()
|
||||
.as_memory()
|
||||
+ common_cfg_cap.bar_offset();
|
||||
let device_cfg_base = space
|
||||
.bar(device_cfg_cap.bar_index().unwrap())
|
||||
.unwrap()
|
||||
.as_memory()
|
||||
+ device_cfg_cap.bar_offset();
|
||||
let device_cfg_len = device_cfg_cap.length();
|
||||
let notify_cfg_base = space
|
||||
.bar(notify_cfg_cap.bar_index().unwrap())
|
||||
.unwrap()
|
||||
.as_memory()
|
||||
+ notify_cfg_cap.bar_offset();
|
||||
let notify_cfg_len = notify_cfg_cap.length();
|
||||
let notify_cfg_mul = notify_cfg_cap.offset_multiplier();
|
||||
|
||||
let common_cfg_base = PhysicalAddress::from_raw(common_cfg_base);
|
||||
let device_cfg_base = PhysicalAddress::from_raw(device_cfg_base);
|
||||
let notify_cfg_base = PhysicalAddress::from_raw(notify_cfg_base);
|
||||
|
||||
assert_eq!(notify_cfg_len % 2, 0);
|
||||
|
||||
let common_cfg = unsafe { DeviceMemoryIo::map(common_cfg_base) }.unwrap();
|
||||
let device_cfg =
|
||||
unsafe { DeviceMemoryIo::map_slice(device_cfg_base, device_cfg_len) }.unwrap();
|
||||
let notify_cfg =
|
||||
unsafe { DeviceMemoryIo::map_slice(notify_cfg_base, notify_cfg_len / 2) }.unwrap();
|
||||
|
||||
Ok(Self {
|
||||
common_cfg,
|
||||
device_cfg,
|
||||
notify_cfg,
|
||||
notify_cfg_mul,
|
||||
})
|
||||
}
|
||||
}
|
24
driver/virtio/net/Cargo.toml
Normal file
24
driver/virtio/net/Cargo.toml
Normal file
@ -0,0 +1,24 @@
|
||||
[package]
|
||||
name = "ygg_driver_virtio_net"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
yggdrasil-abi = { git = "https://git.alnyan.me/yggdrasil/yggdrasil-abi.git" }
|
||||
kernel-util = { path = "../../../lib/kernel-util" }
|
||||
device-api = { path = "../../../lib/device-api", features = ["derive"] }
|
||||
|
||||
ygg_driver_virtio_core = { path = "../core" }
|
||||
ygg_driver_net_core = { path = "../../net/core" }
|
||||
ygg_driver_pci = { path = "../../bus/pci", optional = true }
|
||||
|
||||
log = "0.4.20"
|
||||
bitflags = "2.4.2"
|
||||
tock-registers = "0.8.1"
|
||||
bytemuck = { version = "1.14.0", features = ["derive"] }
|
||||
|
||||
[features]
|
||||
default = []
|
||||
pci = ["ygg_driver_pci", "ygg_driver_virtio_core/pci"]
|
316
driver/virtio/net/src/lib.rs
Normal file
316
driver/virtio/net/src/lib.rs
Normal file
@ -0,0 +1,316 @@
|
||||
// TODO use more fancy features of virtio-net, TCP/IP checksum offloading would be nice
|
||||
#![feature(strict_provenance)]
|
||||
#![no_std]
|
||||
|
||||
extern crate alloc;
|
||||
|
||||
use core::{
|
||||
future::Future,
|
||||
mem::size_of,
|
||||
pin::Pin,
|
||||
ptr::NonNull,
|
||||
task::{Context, Poll},
|
||||
};
|
||||
|
||||
use alloc::{boxed::Box, collections::BTreeMap};
|
||||
use bitflags::Flags;
|
||||
use bytemuck::{Pod, Zeroable};
|
||||
use device_api::{
|
||||
interrupt::{InterruptAffinity, InterruptHandler, MsiHandler},
|
||||
Device,
|
||||
};
|
||||
use kernel_util::{
|
||||
block,
|
||||
mem::{
|
||||
self,
|
||||
address::{FromRaw, IntoRaw, PhysicalAddress},
|
||||
device::{DeviceMemoryIo, RawDeviceMemoryMapping},
|
||||
PageBox,
|
||||
},
|
||||
message_interrupt_controller,
|
||||
runtime::{self, QueueWaker},
|
||||
sync::{spin_rwlock::IrqSafeRwLock, IrqSafeSpinlock, IrqSafeSpinlockGuard},
|
||||
util::{ring::RingBuffer, OneTimeInit},
|
||||
};
|
||||
use ygg_driver_net_core::{interface::NetworkDevice, types::MacAddress, Packet};
|
||||
use ygg_driver_pci::{
|
||||
capability::{
|
||||
MsiXCapability, MsiXVectorTable, VirtioCapabilityData, VirtioCommonConfigCapability,
|
||||
VirtioDeviceConfigCapability, VirtioNotifyConfigCapability,
|
||||
},
|
||||
PciBaseAddress, PciCommandRegister, PciConfigurationSpace, PciDeviceInfo,
|
||||
};
|
||||
use ygg_driver_virtio_core::{
|
||||
queue::VirtQueue,
|
||||
transport::{pci::PciTransport, Transport},
|
||||
DeviceStatus,
|
||||
};
|
||||
use yggdrasil_abi::error::Error;
|
||||
|
||||
struct Queues {
|
||||
receive: IrqSafeSpinlock<VirtQueue>,
|
||||
transmit: IrqSafeSpinlock<VirtQueue>,
|
||||
|
||||
configuration_vector: usize,
|
||||
receive_vector: usize,
|
||||
}
|
||||
|
||||
pub struct VirtioNet<T: Transport> {
|
||||
transport: IrqSafeSpinlock<T>,
|
||||
queues: OneTimeInit<Queues>,
|
||||
interface_id: OneTimeInit<u32>,
|
||||
|
||||
mac: IrqSafeRwLock<MacAddress>,
|
||||
|
||||
pending_packets: IrqSafeRwLock<BTreeMap<u16, PageBox<[u8]>>>,
|
||||
pending_tx_packets: IrqSafeRwLock<BTreeMap<u16, PageBox<[u8]>>>,
|
||||
|
||||
vector_table: IrqSafeRwLock<MsiXVectorTable<'static>>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Pod, Zeroable)]
|
||||
#[repr(C)]
|
||||
struct VirtioPacketHeader {
|
||||
flags: u8,
|
||||
gso_type: u8,
|
||||
hdr_len: u16,
|
||||
gso_size: u16,
|
||||
csum_start: u16,
|
||||
csum_offset: u16,
|
||||
}
|
||||
|
||||
impl Queues {
|
||||
pub fn try_receive(&self, _index: usize) -> Option<(u16, IrqSafeSpinlockGuard<VirtQueue>)> {
|
||||
let mut queue = self.receive.lock();
|
||||
let (token, _) = unsafe { queue.pop_last_used() }?;
|
||||
Some((token, queue))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Transport> VirtioNet<T> {
|
||||
const PACKET_SIZE: usize = 4096;
|
||||
|
||||
pub fn new(mut transport: T, vector_table: MsiXVectorTable<'static>) -> Self {
|
||||
// Read MAC from device config
|
||||
let device_cfg = transport
|
||||
.device_cfg()
|
||||
.expect("virtio-net must have device-specific configuration section");
|
||||
let mut mac_bytes = [0; 6];
|
||||
mac_bytes.copy_from_slice(&device_cfg[..6]);
|
||||
let mac = MacAddress::from(mac_bytes);
|
||||
|
||||
Self {
|
||||
transport: IrqSafeSpinlock::new(transport),
|
||||
queues: OneTimeInit::new(),
|
||||
interface_id: OneTimeInit::new(),
|
||||
|
||||
mac: IrqSafeRwLock::new(mac),
|
||||
|
||||
pending_packets: IrqSafeRwLock::new(BTreeMap::new()),
|
||||
pending_tx_packets: IrqSafeRwLock::new(BTreeMap::new()),
|
||||
vector_table: IrqSafeRwLock::new(vector_table),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn listen(&self, buffers: usize) {
|
||||
let queues = self.queues.get();
|
||||
let mut queue = queues.receive.lock();
|
||||
let mut packets = self.pending_packets.write();
|
||||
|
||||
for _ in 0..buffers {
|
||||
let mut packet = PageBox::new_slice(0, Self::PACKET_SIZE).unwrap();
|
||||
let token = unsafe { queue.add(&[&mut packet], &[]).unwrap() };
|
||||
packets.insert(token, packet);
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_receive_interrupt(&self, queue: usize) -> bool {
|
||||
let queues = self.queues.get();
|
||||
let interface_id = *self.interface_id.get();
|
||||
let mut count = 0;
|
||||
|
||||
while let Some((token, mut queue)) = queues.try_receive(queue) {
|
||||
let mut pending_packets = self.pending_packets.write();
|
||||
let packet = pending_packets.remove(&token).unwrap();
|
||||
|
||||
let mut buffer = PageBox::new_slice(0, Self::PACKET_SIZE).unwrap();
|
||||
|
||||
let token = unsafe { queue.add(&[&mut buffer], &[]).unwrap() };
|
||||
pending_packets.insert(token, buffer);
|
||||
|
||||
let packet = Packet::new(packet, size_of::<VirtioPacketHeader>(), interface_id);
|
||||
ygg_driver_net_core::receive_packet(packet).unwrap();
|
||||
count += 1
|
||||
}
|
||||
|
||||
if count != 0 {
|
||||
self.transport.lock().notify(1);
|
||||
}
|
||||
|
||||
count != 0
|
||||
}
|
||||
|
||||
fn begin_init(&self) -> Result<DeviceStatus, Error> {
|
||||
let mut transport = self.transport.lock();
|
||||
let mut status = DeviceStatus::RESET_VALUE;
|
||||
|
||||
log::debug!("Reset device");
|
||||
transport.write_device_status(status);
|
||||
status |= DeviceStatus::ACKNOWLEDGE;
|
||||
transport.write_device_status(status);
|
||||
status |= DeviceStatus::DRIVER;
|
||||
transport.write_device_status(status);
|
||||
|
||||
let device_features = transport.read_device_features();
|
||||
|
||||
// TODO blah blah blah
|
||||
|
||||
transport.write_driver_features(0);
|
||||
|
||||
status |= DeviceStatus::FEATURES_OK;
|
||||
transport.write_device_status(status);
|
||||
|
||||
if !transport
|
||||
.read_device_status()
|
||||
.contains(DeviceStatus::FEATURES_OK)
|
||||
{
|
||||
return Err(Error::InvalidOperation);
|
||||
}
|
||||
|
||||
Ok(status)
|
||||
}
|
||||
|
||||
fn finish_init(&self, status: DeviceStatus) {
|
||||
let mut transport = self.transport.lock();
|
||||
|
||||
transport.write_device_status(status | DeviceStatus::DRIVER_OK);
|
||||
}
|
||||
|
||||
unsafe fn setup_queues(
|
||||
&'static self,
|
||||
receive_count: usize,
|
||||
transmit_count: usize,
|
||||
) -> Result<(), Error> {
|
||||
// TODO multiqueue capability
|
||||
assert_eq!(receive_count, 1);
|
||||
assert_eq!(transmit_count, 1);
|
||||
|
||||
let mut transport = self.transport.lock();
|
||||
let mut vt = self.vector_table.write();
|
||||
|
||||
let msix_range = vt.register_range(
|
||||
0,
|
||||
1 + receive_count,
|
||||
message_interrupt_controller(),
|
||||
InterruptAffinity::Any,
|
||||
self,
|
||||
)?;
|
||||
|
||||
// TODO set the configuration vector in virtio common cfg
|
||||
let receive_vector: u16 = msix_range[1].vector.try_into().unwrap();
|
||||
|
||||
// Setup the virtqs
|
||||
let rx = VirtQueue::with_max_capacity(&mut *transport, 0, 128, Some(receive_vector))
|
||||
.map_err(cvt_error)?;
|
||||
let tx = VirtQueue::with_max_capacity(&mut *transport, 1, 128, None).map_err(cvt_error)?;
|
||||
|
||||
self.queues.init(Queues {
|
||||
receive: IrqSafeSpinlock::new(rx),
|
||||
transmit: IrqSafeSpinlock::new(tx),
|
||||
|
||||
configuration_vector: msix_range[0].vector,
|
||||
receive_vector: msix_range[1].vector,
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Transport + 'static> NetworkDevice for VirtioNet<T> {
|
||||
fn transmit(&self, mut packet: PageBox<[u8]>) -> Result<(), Error> {
|
||||
let queues = self.queues.get();
|
||||
let mut tx = queues.transmit.lock();
|
||||
let mut transport = self.transport.lock();
|
||||
packet[..size_of::<VirtioPacketHeader>()].fill(0);
|
||||
let len = tx
|
||||
.add_notify_wait_pop(&[], &[&packet], &mut *transport)
|
||||
.unwrap();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn read_hardware_address(&self) -> MacAddress {
|
||||
*self.mac.read()
|
||||
}
|
||||
|
||||
fn packet_prefix_size(&self) -> usize {
|
||||
size_of::<VirtioPacketHeader>()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Transport + 'static> MsiHandler for VirtioNet<T> {
|
||||
fn handle_msi(&self, vector: usize) -> bool {
|
||||
let Some(queues) = self.queues.try_get() else {
|
||||
return false;
|
||||
};
|
||||
|
||||
if vector == queues.receive_vector {
|
||||
self.handle_receive_interrupt(0)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Transport + 'static> Device for VirtioNet<T> {
|
||||
fn display_name(&self) -> &'static str {
|
||||
"VirtIO Network Device"
|
||||
}
|
||||
|
||||
unsafe fn init(&'static self) -> Result<(), Error> {
|
||||
let status = self.begin_init()?;
|
||||
|
||||
// TODO multiqueue
|
||||
self.setup_queues(1, 1)?;
|
||||
|
||||
self.finish_init(status);
|
||||
|
||||
let id = ygg_driver_net_core::register_interface(self);
|
||||
self.interface_id.init(id);
|
||||
self.listen(64);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
unsafe fn init_irq(&'static self) -> Result<(), Error> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn cvt_error(error: ygg_driver_virtio_core::error::Error) -> Error {
|
||||
use ygg_driver_virtio_core::error::Error as VirtioError;
|
||||
match error {
|
||||
VirtioError::OsError(err) => err,
|
||||
_ => Error::InvalidOperation,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn probe(info: &PciDeviceInfo) -> Result<&'static dyn Device, Error> {
|
||||
let space = &info.config_space;
|
||||
|
||||
let mut msix = space.capability::<MsiXCapability>().unwrap();
|
||||
let mut vt = msix.vector_table()?;
|
||||
|
||||
// TODO is this really needed? PCI spec says this is masked on reset, though I'm not sure if
|
||||
// firmware puts it back in masked state after loading the kernel
|
||||
vt.mask_all();
|
||||
msix.set_function_mask(false);
|
||||
msix.set_enabled(true);
|
||||
|
||||
let transport = PciTransport::from_config_space(space).unwrap();
|
||||
let device = VirtioNet::new(transport, vt);
|
||||
|
||||
let device = Box::leak(Box::new(device));
|
||||
|
||||
Ok(device)
|
||||
}
|
@ -27,6 +27,8 @@ extern "Rust" {
|
||||
pub fn __virtualize(phys: u64) -> usize;
|
||||
pub fn __physicalize(virt: usize) -> u64;
|
||||
|
||||
pub fn __translate_kernel(virt: usize) -> Option<PhysicalAddress>;
|
||||
|
||||
pub fn __map_device_pages(
|
||||
base: PhysicalAddress,
|
||||
count: usize,
|
||||
|
@ -2,6 +2,7 @@ use core::{
|
||||
alloc::Layout,
|
||||
mem::size_of,
|
||||
ops::{Deref, DerefMut},
|
||||
ptr::NonNull,
|
||||
};
|
||||
|
||||
use alloc::sync::Arc;
|
||||
@ -66,6 +67,39 @@ impl RawDeviceMemoryMapping {
|
||||
core::mem::forget(self);
|
||||
address
|
||||
}
|
||||
|
||||
pub fn into_raw_parts(self) -> (usize, usize, usize, usize) {
|
||||
let address = self.address;
|
||||
let base_address = self.base_address;
|
||||
let page_count = self.page_count;
|
||||
let page_size = self.page_size;
|
||||
|
||||
core::mem::forget(self);
|
||||
|
||||
(address, base_address, page_count, page_size)
|
||||
}
|
||||
|
||||
pub unsafe fn from_raw_parts(
|
||||
address: usize,
|
||||
base_address: usize,
|
||||
page_count: usize,
|
||||
page_size: usize,
|
||||
) -> Self {
|
||||
Self {
|
||||
address,
|
||||
base_address,
|
||||
page_count,
|
||||
page_size,
|
||||
}
|
||||
}
|
||||
|
||||
/// "Casts" the mapping to a specific type T and returns a [NonNull] pointer to it
|
||||
pub unsafe fn as_non_null<T>(&self) -> NonNull<T> {
|
||||
if self.page_size * self.page_count < size_of::<T>() {
|
||||
panic!();
|
||||
}
|
||||
NonNull::new_unchecked(self.address as *mut T)
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for RawDeviceMemoryMapping {
|
||||
|
@ -5,11 +5,12 @@ use core::{
|
||||
ops::{Deref, DerefMut},
|
||||
};
|
||||
|
||||
use alloc::boxed::Box;
|
||||
use yggdrasil_abi::error::Error;
|
||||
|
||||
use crate::api::{self, __allocate_contiguous_pages, __free_page, __physicalize};
|
||||
|
||||
use self::address::{AsPhysicalAddress, PhysicalAddress};
|
||||
use self::address::{AsPhysicalAddress, FromRaw, PhysicalAddress};
|
||||
|
||||
pub mod address;
|
||||
pub mod device;
|
||||
@ -258,3 +259,11 @@ unsafe impl<T: ?Sized + Sync> Sync for PageBox<T> {}
|
||||
pub fn allocate_page() -> Result<PhysicalAddress, Error> {
|
||||
unsafe { api::__allocate_page() }
|
||||
}
|
||||
|
||||
pub fn allocate_contiguous_pages(count: usize) -> Result<PhysicalAddress, Error> {
|
||||
unsafe { api::__allocate_contiguous_pages(count) }
|
||||
}
|
||||
|
||||
pub fn translate_kernel_address(virt: usize) -> Option<PhysicalAddress> {
|
||||
unsafe { api::__translate_kernel(virt) }
|
||||
}
|
||||
|
@ -89,12 +89,9 @@ impl<T> RingBuffer<T> {
|
||||
///
|
||||
/// The caller must perform the necessary checks to avoid reading beyond the write head.
|
||||
#[inline]
|
||||
pub unsafe fn read_single_unchecked(&mut self) -> T
|
||||
where
|
||||
T: Copy,
|
||||
{
|
||||
pub unsafe fn read_single_unchecked(&mut self) -> T {
|
||||
let data = self.data.as_ref().unwrap();
|
||||
let res = data[self.rd].assume_init();
|
||||
let res = data[self.rd].assume_init_read();
|
||||
self.rd = (self.rd + 1) % self.capacity;
|
||||
res
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ use core::{
|
||||
any::Any,
|
||||
fmt,
|
||||
mem::MaybeUninit,
|
||||
net::SocketAddr,
|
||||
task::{Context, Poll},
|
||||
};
|
||||
|
||||
@ -24,8 +25,10 @@ use crate::{
|
||||
channel::ChannelDescriptor,
|
||||
device::{BlockDeviceWrapper, CharDeviceWrapper},
|
||||
node::NodeRef,
|
||||
socket::PacketSocketWrapper,
|
||||
traits::{Read, Seek, Write},
|
||||
FdPoll, FileReadiness, PseudoTerminal, PseudoTerminalMaster, PseudoTerminalSlave, SharedMemory,
|
||||
FdPoll, FileReadiness, PacketSocket, PseudoTerminal, PseudoTerminalMaster, PseudoTerminalSlave,
|
||||
SharedMemory, Socket,
|
||||
};
|
||||
|
||||
use self::{
|
||||
@ -63,6 +66,8 @@ pub enum File {
|
||||
Block(BlockFile),
|
||||
Char(CharFile),
|
||||
|
||||
PacketSocket(Arc<PacketSocketWrapper>),
|
||||
|
||||
AnonymousPipe(PipeEnd),
|
||||
Poll(FdPoll),
|
||||
Channel(ChannelDescriptor),
|
||||
@ -115,6 +120,11 @@ impl File {
|
||||
))
|
||||
}
|
||||
|
||||
/// Constructs a [File] from a [PacketSocket]
|
||||
pub fn from_packet_socket(socket: Arc<dyn PacketSocket>) -> Arc<Self> {
|
||||
Arc::new(Self::PacketSocket(Arc::new(PacketSocketWrapper(socket))))
|
||||
}
|
||||
|
||||
pub(crate) fn directory(node: NodeRef, position: DirectoryOpenPosition) -> Arc<Self> {
|
||||
let position = IrqSafeSpinlock::new(position.into());
|
||||
Arc::new(Self::Directory(DirectoryFile { node, position }))
|
||||
@ -261,6 +271,39 @@ impl File {
|
||||
Err(Error::InvalidOperation)
|
||||
}
|
||||
}
|
||||
|
||||
/// Interprets the file as a packet-based socket
|
||||
pub fn as_packet_socket(&self) -> Result<&PacketSocketWrapper, Error> {
|
||||
if let Self::PacketSocket(sock) = self {
|
||||
Ok(sock)
|
||||
} else {
|
||||
Err(Error::InvalidOperation)
|
||||
}
|
||||
}
|
||||
|
||||
/// Sends data to a socket
|
||||
pub fn send_to(&self, buffer: &[u8], recepient: Option<SocketAddr>) -> Result<usize, Error> {
|
||||
match recepient {
|
||||
Some(recepient) => self.as_packet_socket()?.send(recepient, buffer),
|
||||
None => todo!(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Receives data from a socket
|
||||
pub fn receive_from(
|
||||
&self,
|
||||
buffer: &mut [u8],
|
||||
remote: &mut MaybeUninit<SocketAddr>,
|
||||
) -> Result<usize, Error> {
|
||||
match self {
|
||||
Self::PacketSocket(socket) => {
|
||||
let (addr, len) = socket.receive(buffer)?;
|
||||
remote.write(addr);
|
||||
Ok(len)
|
||||
}
|
||||
_ => Err(Error::InvalidOperation),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PageProvider for File {
|
||||
@ -295,6 +338,7 @@ impl Read for File {
|
||||
// TODO maybe allow reading messages from Channels?
|
||||
Self::Channel(_) => Err(Error::InvalidOperation),
|
||||
Self::SharedMemory(_) => Err(Error::InvalidOperation),
|
||||
Self::PacketSocket(_) => Err(Error::InvalidOperation),
|
||||
Self::Directory(_) => Err(Error::IsADirectory),
|
||||
}
|
||||
}
|
||||
@ -314,6 +358,7 @@ impl Write for File {
|
||||
// TODO maybe allow writing messages to Channels?
|
||||
Self::Channel(_) => Err(Error::InvalidOperation),
|
||||
Self::SharedMemory(_) => Err(Error::InvalidOperation),
|
||||
Self::PacketSocket(_) => Err(Error::InvalidOperation),
|
||||
Self::Directory(_) => Err(Error::IsADirectory),
|
||||
}
|
||||
}
|
||||
@ -366,6 +411,11 @@ impl fmt::Debug for File {
|
||||
Self::SharedMemory(_) => f.debug_struct("SharedMemory").finish_non_exhaustive(),
|
||||
Self::PtySlave(_) => f.debug_struct("PtySlave").finish_non_exhaustive(),
|
||||
Self::PtyMaster(_) => f.debug_struct("PtyMaster").finish_non_exhaustive(),
|
||||
Self::PacketSocket(sock) => f
|
||||
.debug_struct("PacketSocket")
|
||||
.field("local", &sock.local_address())
|
||||
.field("remote", &sock.remote_address())
|
||||
.finish_non_exhaustive(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -19,6 +19,7 @@ pub(crate) mod path;
|
||||
pub(crate) mod poll;
|
||||
pub(crate) mod pty;
|
||||
pub(crate) mod shared_memory;
|
||||
pub(crate) mod socket;
|
||||
pub(crate) mod traits;
|
||||
|
||||
pub use channel::MessagePayload;
|
||||
@ -32,4 +33,5 @@ pub use node::{
|
||||
pub use poll::FdPoll;
|
||||
pub use pty::{PseudoTerminal, PseudoTerminalMaster, PseudoTerminalSlave};
|
||||
pub use shared_memory::SharedMemory;
|
||||
pub use socket::{ConnectionSocket, ListenerSocket, PacketSocket, Socket};
|
||||
pub use traits::{FileReadiness, Read, Seek, Write};
|
||||
|
@ -292,6 +292,10 @@ where
|
||||
let instance = instance.unwrap().downcast_ref::<FnNodeData>().unwrap();
|
||||
Ok(instance.as_write()?.lock().write_slice(pos as _, buf))
|
||||
}
|
||||
|
||||
fn truncate(&self, _node: &NodeRef, _new_size: u64) -> Result<(), Error> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
// Byte read-only node
|
||||
|
53
lib/vfs/src/socket.rs
Normal file
53
lib/vfs/src/socket.rs
Normal file
@ -0,0 +1,53 @@
|
||||
use core::{
|
||||
net::SocketAddr,
|
||||
ops::Deref,
|
||||
task::{Context, Poll},
|
||||
};
|
||||
|
||||
use alloc::sync::Arc;
|
||||
use yggdrasil_abi::error::Error;
|
||||
|
||||
use crate::FileReadiness;
|
||||
|
||||
/// Interface for interacting with network sockets
|
||||
pub trait Socket: FileReadiness + Send {
|
||||
/// Socket listen/receive address
|
||||
fn local_address(&self) -> SocketAddr;
|
||||
|
||||
/// Socket remote address
|
||||
fn remote_address(&self) -> Option<SocketAddr>;
|
||||
|
||||
/// Closes a socket
|
||||
fn close(&self) -> Result<(), Error>;
|
||||
}
|
||||
|
||||
/// Stateless/packet-based socket interface
|
||||
pub trait PacketSocket: Socket {
|
||||
/// Receives a packet into provided buffer. Will return an error if packet cannot be placed
|
||||
/// within the buffer.
|
||||
fn receive(&self, buffer: &mut [u8]) -> Result<(SocketAddr, usize), Error>;
|
||||
|
||||
/// Sends provided data to the recepient specified by `destination`
|
||||
fn send(&self, destination: SocketAddr, data: &[u8]) -> Result<usize, Error>;
|
||||
}
|
||||
|
||||
/// Connection-based client socket interface
|
||||
pub trait ConnectionSocket: Socket {}
|
||||
/// Connection-based listener socket interface
|
||||
pub trait ListenerSocket: Socket {}
|
||||
|
||||
pub struct PacketSocketWrapper(pub Arc<dyn PacketSocket + 'static>);
|
||||
|
||||
impl Deref for PacketSocketWrapper {
|
||||
type Target = dyn PacketSocket;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
self.0.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for PacketSocketWrapper {
|
||||
fn drop(&mut self) {
|
||||
self.0.close().ok();
|
||||
}
|
||||
}
|
@ -122,6 +122,9 @@ pub trait Architecture {
|
||||
/// Converts a virtual address created by [Architecture::virtualize] back to its physical form
|
||||
fn physicalize(address: usize) -> u64;
|
||||
|
||||
/// Translates a kernel virtual address into its physical page
|
||||
fn translate_kernel_address(address: usize) -> Option<PhysicalAddress>;
|
||||
|
||||
// Architecture intrinsics
|
||||
|
||||
/// Suspends CPU until an interrupt is received
|
||||
@ -356,3 +359,8 @@ fn __monotonic_timestamp() -> Result<Duration, Error> {
|
||||
fn __message_interrupt_controller() -> &'static dyn MessageInterruptController {
|
||||
ARCHITECTURE.message_interrupt_controller()
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
fn __translate_kernel(address: usize) -> Option<PhysicalAddress> {
|
||||
ArchitectureImpl::translate_kernel_address(address)
|
||||
}
|
||||
|
@ -311,6 +311,23 @@ fn clone_kernel_tables(dst: &mut PageTable<L0>) {
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn translate_kernel_address(address: usize) -> Option<PhysicalAddress> {
|
||||
let l0i = address.page_index::<L0>();
|
||||
let l1i = address.page_index::<L1>();
|
||||
let l2i = address.page_index::<L2>();
|
||||
let l3i = address.page_index::<L3>();
|
||||
|
||||
match l0i {
|
||||
KERNEL_L0_INDEX => match l1i {
|
||||
HEAP_MAPPING_L1I => Some(PhysicalAddress::from_raw(address - HEAP_MAPPING_OFFSET)),
|
||||
DEVICE_MAPPING_L1I => todo!(),
|
||||
_ => todo!(),
|
||||
},
|
||||
RAM_MAPPING_L0I => todo!(),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets up the following memory map:
|
||||
/// ...: KERNEL_TABLES.l0:
|
||||
/// * 0xFFFFFF0000000000 .. 0xFFFFFFFF8000000000 : RAM_MAPPING_L1
|
||||
|
@ -275,6 +275,11 @@ impl Architecture for X86_64 {
|
||||
(address - RAM_MAPPING_OFFSET) as _
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn translate_kernel_address(address: usize) -> Option<PhysicalAddress> {
|
||||
mem::translate_kernel_address(address)
|
||||
}
|
||||
|
||||
fn external_interrupt_controller(
|
||||
&'static self,
|
||||
) -> &'static dyn ExternalInterruptController<IrqNumber = Self::IrqNumber> {
|
||||
@ -394,6 +399,12 @@ impl X86_64 {
|
||||
Some(0x01),
|
||||
ygg_driver_ahci::probe,
|
||||
);
|
||||
ygg_driver_pci::register_vendor_driver(
|
||||
"Virtio PCI Network Device",
|
||||
0x1AF4,
|
||||
0x1000,
|
||||
ygg_driver_virtio_net::probe,
|
||||
);
|
||||
|
||||
match self.boot_data.get() {
|
||||
&BootData::YBoot(data) => {
|
||||
|
@ -28,6 +28,8 @@ fn setup_root() -> Result<NodeRef, Error> {
|
||||
pub fn kinit() -> Result<(), Error> {
|
||||
infoln!("In main");
|
||||
|
||||
ygg_driver_net_core::start_network_tasks()?;
|
||||
|
||||
#[cfg(feature = "fb_console")]
|
||||
{
|
||||
use crate::device::display::console::update_consoles_task;
|
||||
|
@ -1,5 +1,5 @@
|
||||
//! System function call handlers
|
||||
use core::{mem::MaybeUninit, time::Duration};
|
||||
use core::{mem::MaybeUninit, net::SocketAddr, time::Duration};
|
||||
|
||||
use abi::{
|
||||
error::Error,
|
||||
@ -9,6 +9,7 @@ use abi::{
|
||||
TerminalOptions, TerminalSize,
|
||||
},
|
||||
mem::MappingSource,
|
||||
net::SocketType,
|
||||
process::{
|
||||
ExecveOptions, ExitCode, MutexOperation, Signal, SpawnOption, SpawnOptions,
|
||||
ThreadSpawnOptions,
|
||||
@ -18,6 +19,7 @@ use abi::{
|
||||
use alloc::{borrow::ToOwned, boxed::Box, sync::Arc, vec::Vec};
|
||||
use kernel_util::{block, mem::table::EntryLevelExt, runtime, sync::IrqSafeSpinlockGuard};
|
||||
use vfs::{File, IoContext, MessagePayload, NodeRef, Read, Seek, Write};
|
||||
use ygg_driver_net_core::socket::UdpSocket;
|
||||
use yggdrasil_abi::{error::SyscallResult, io::MountOptions};
|
||||
|
||||
use crate::{
|
||||
@ -658,6 +660,45 @@ fn syscall_handler(func: SyscallFunction, args: &[u64]) -> Result<usize, Error>
|
||||
Ok(0)
|
||||
}
|
||||
|
||||
// Networking
|
||||
SyscallFunction::BindSocket => {
|
||||
let listen = arg_user_ref::<SocketAddr>(args[0] as usize)?;
|
||||
let ty = SocketType::try_from(args[1] as u32).map_err(|_| Error::InvalidArgument)?;
|
||||
|
||||
run_with_io(process, |mut io| {
|
||||
let socket = match ty {
|
||||
SocketType::UdpPacket => UdpSocket::bind(*listen)?,
|
||||
_ => todo!(),
|
||||
};
|
||||
let file = File::from_packet_socket(socket);
|
||||
let fd = io.files.place_file(file, true)?;
|
||||
Ok(fd.0 as usize)
|
||||
})
|
||||
}
|
||||
SyscallFunction::ConnectSocket => todo!(),
|
||||
SyscallFunction::SendTo => {
|
||||
let socket_fd = RawFd::from(args[0] as u32);
|
||||
let buffer = arg_buffer_ref(args[1] as usize, args[2] as usize)?;
|
||||
let recepient = arg_user_ref::<Option<SocketAddr>>(args[3] as usize)?;
|
||||
|
||||
run_with_io(process, |mut io| {
|
||||
let file = io.files.file(socket_fd)?;
|
||||
|
||||
file.send_to(buffer, *recepient)
|
||||
})
|
||||
}
|
||||
SyscallFunction::ReceiveFrom => {
|
||||
let socket_fd = RawFd::from(args[0] as u32);
|
||||
let buffer = arg_buffer_mut(args[1] as usize, args[2] as usize)?;
|
||||
let remote = arg_user_mut::<MaybeUninit<SocketAddr>>(args[3] as usize)?;
|
||||
|
||||
run_with_io(process, |mut io| {
|
||||
let file = io.files.file(socket_fd)?;
|
||||
|
||||
file.receive_from(buffer, remote)
|
||||
})
|
||||
}
|
||||
|
||||
SyscallFunction::Fork => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user