cherry-pick jiegec's work (#3) from lab8-rv32: virtio drivers, network stack

Read and parse dtb upon boot

Implement virtio net device detection

Negotiate feature bits for virtio-net

Read MAC address from virtio-net device and detect virtqueues

Receiving from virtio net device is working for the first time

Implement driver interface and interrupt handling routines

Rearrange drivers into upper directory

Implement initial support for processing arp request and reply packet

Refactor MAC address and IPv4 address into structs, and implement ICMP echo reply

Fix typos

Implement initial support for virtio gpu driver

Complete first working version of virtio gpu driver with mandelbrot example

Use smoltcp and implement a udp and tcp server on top of it

Cleanup virtio net code
master
WangRunji 6 years ago
parent 353850f41d
commit 047f4ffdcc

@ -42,6 +42,16 @@ pub trait PageTable {
fn write(&mut self, addr: VirtAddr, data: u8) {
unsafe { (addr as *mut u8).write(data) }
}
/// When 'vaddr' is not mapped, map it to 'paddr'.
fn map_if_not_exists(&mut self, vaddr: VirtAddr, paddr: usize) -> bool {
if let None = self.get_entry(vaddr) {
self.map(vaddr, paddr);
true
} else {
false
}
}
}
/// Page Table Entry

41
kernel/Cargo.lock generated

@ -86,6 +86,11 @@ dependencies = [
"xmas-elf 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "byteorder"
version = "1.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "cc"
version = "1.0.25"
@ -96,6 +101,11 @@ name = "cfg-if"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "device_tree"
version = "1.0.3"
source = "git+https://github.com/jiegec/device_tree-rs#1bd9b4df529a6ef8cc49006fe0c96c92c17c894d"
[[package]]
name = "fixedvec"
version = "0.2.3"
@ -147,6 +157,14 @@ dependencies = [
"spin 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "log"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "log"
version = "0.4.6"
@ -155,6 +173,11 @@ dependencies = [
"cfg-if 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "managed"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "nodrop"
version = "0.1.13"
@ -217,6 +240,7 @@ dependencies = [
"bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
"bootloader 0.3.4 (git+https://github.com/wangrunji0408/bootloader)",
"cc 1.0.25 (registry+https://github.com/rust-lang/crates.io-index)",
"device_tree 1.0.3 (git+https://github.com/jiegec/device_tree-rs)",
"lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"linked_list_allocator 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
@ -227,6 +251,7 @@ dependencies = [
"rcore-process 0.1.0",
"riscv 0.3.0 (git+https://github.com/riscv-and-rust-and-decaf/riscv)",
"simple-filesystem 0.1.0 (git+https://github.com/wangrunji0408/SimpleFileSystem-Rust)",
"smoltcp 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
"spin 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)",
"uart_16550 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"volatile 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)",
@ -316,6 +341,17 @@ dependencies = [
"tempdir 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "smoltcp"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
"byteorder 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
"managed 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "spin"
version = "0.4.10"
@ -444,8 +480,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
"checksum bit_field 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ed8765909f9009617974ab6b7d332625b320b33c326b1e9321382ef1999b5d56"
"checksum bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "228047a76f468627ca71776ecdebd732a3423081fcf5125585bcd7c49886ce12"
"checksum bootloader 0.3.4 (git+https://github.com/wangrunji0408/bootloader)" = "<none>"
"checksum byteorder 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a019b10a2a7cdeb292db131fc8113e57ea2a908f6e7894b0c3c671893b65dbeb"
"checksum cc 1.0.25 (registry+https://github.com/rust-lang/crates.io-index)" = "f159dfd43363c4d08055a07703eb7a3406b0dac4d0584d96965a3262db3c9d16"
"checksum cfg-if 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "082bb9b28e00d3c9d39cc03e64ce4cea0f1bb9b3fde493f0cbc008472d22bdf4"
"checksum device_tree 1.0.3 (git+https://github.com/jiegec/device_tree-rs)" = "<none>"
"checksum fixedvec 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "7c6c16d316ccdac21a4dd648e314e76facbbaf316e83ca137d0857a9c07419d0"
"checksum fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82"
"checksum fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7"
@ -453,7 +491,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
"checksum lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a374c89b9db55895453a74c1e38861d9deec0b01b405a82516e9d5de4820dea1"
"checksum libc 0.2.44 (registry+https://github.com/rust-lang/crates.io-index)" = "10923947f84a519a45c8fefb7dd1b3e8c08747993381adee176d7a82b4195311"
"checksum linked_list_allocator 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)" = "655d57c71827fe0891ce72231b6aa5e14033dae3f604609e6a6f807267c1678d"
"checksum log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "e19e8d5c34a3e0e2223db8e060f9e8264aeeb5c5fc64a4ee9965c062211c024b"
"checksum log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c84ec4b527950aa83a329754b01dbe3f58361d1c5efacd1f6d68c494d08a17c6"
"checksum managed 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "fdcec5e97041c7f0f1c5b7d93f12e57293c831c646f4cc7a5db59460c7ea8de6"
"checksum nodrop 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)" = "2f9667ddcc6cc8a43afc9b7917599d7216aa09c463919ea32c59ed6cac8bc945"
"checksum once 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "931fb7a4cf34610cf6cbe58d52a8ca5ef4c726d4e2e178abd0dc13a6551c6d73"
"checksum os_bootinfo 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "66481dbeb5e773e7bd85b63cd6042c30786f834338288c5ec4f3742673db360a"
@ -469,6 +509,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
"checksum semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
"checksum simple-filesystem 0.1.0 (git+https://github.com/wangrunji0408/SimpleFileSystem-Rust)" = "<none>"
"checksum skeptic 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "061203a849117b0f7090baf8157aa91dac30545208fbb85166ac58b4ca33d89c"
"checksum smoltcp 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fef582369edb298c6c41319a544ca9c4e83622f226055ccfcb35974fbb55ed34"
"checksum spin 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)" = "ceac490aa12c567115b40b7b7fceca03a6c9d53d5defea066123debc83c5dc1f"
"checksum static_assertions 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "389ce475f424f267dbed6479cbd8f126c5e1afb053b0acdaa019c74305fc65d1"
"checksum tempdir 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)" = "15f2b5fb00ccdf689e0149d1b1b3c03fead81c2b37735d812fa8bddbbf41b6d8"

@ -42,7 +42,9 @@ bitflags = "1.0"
bit_field = "0.9"
volatile = "0.2"
linked_list_allocator = "0.6"
device_tree = { git = "https://github.com/jiegec/device_tree-rs" }
lazy_static = { version = "1.2", features = ["spin_no_std"] }
smoltcp = { version = "0.5.0", default-features = false, features = ["alloc", "log", "proto-ipv4", "proto-igmp", "socket-icmp", "socket-udp", "socket-tcp"] }
bit-allocator = { path = "../crate/bit-allocator" }
rcore-memory = { path = "../crate/memory" }
rcore-process = { path = "../crate/process" }

@ -25,6 +25,7 @@ arch ?= riscv32
board ?= none
mode ?= debug
LOG ?= debug
graphic ?= off
smp ?= 4
m_mode ?=
@ -101,6 +102,12 @@ ifdef d
qemu_opts += -d $(d)
endif
ifeq ($(graphic), off)
qemu_opts += -nographic
else
qemu_opts += -serial stdio
endif
### build args ###
ifneq ($(graphic), on)
features += nographic
@ -152,10 +159,10 @@ objdump := $(prefix)objdump
objcopy := $(prefix)objcopy
cc := $(prefix)gcc
as := $(prefix)as
gdb := $(prefix)gdb
gdb := gdb
export CC=$(cc)
.PHONY: all clean run build asm doc justrun debug kernel sfsimg install
.PHONY: all clean run build asm doc justrun debug kernel sfsimg install runnet
all: kernel
@ -167,10 +174,21 @@ doc:
@cargo rustdoc -- --document-private-items
run: build justrun
runnet: build justrunnet
runui: build justrunui
justrun:
@qemu-system-$(arch) $(qemu_opts)
justrunnet: build
@sudo qemu-system-$(arch) $(qemu_opts) \
-netdev type=tap,id=net0,script=no,downscript=no \
-device virtio-net-device,netdev=net0 \
justrunui: build
@qemu-system-$(arch) $(qemu_opts) \
-device virtio-gpu-device \
debug: $(kernel) $(bin)
@qemu-system-$(arch) $(qemu_opts) -s -S &
@sleep 1

@ -6,3 +6,7 @@ pub fn id() -> usize {
// TODO: cpu id
0
}
pub fn fence() {
unsafe { asm!("dmb ish" ::: "memory"); }
}

@ -43,3 +43,5 @@ pub const MEMORY_END: usize = 0x8100_0000;
pub const USER_STACK_OFFSET: usize = 0x70000000;
pub const USER_STACK_SIZE: usize = 0x10000;
pub const USER32_STACK_OFFSET: usize = USER_STACK_OFFSET;
pub const MAX_DTB_SIZE: usize = 0x2000;

@ -35,3 +35,7 @@ pub unsafe fn start_others(hart_mask: usize) {
pub fn halt() {
unsafe { riscv::asm::wfi() }
}
pub fn fence() {
unsafe { asm!("fence" ::: "memory"); }
}

@ -11,6 +11,7 @@ use riscv::register::{
stvec as xtvec,
};
use riscv::register::{mcause, mepc, sie, mie};
use crate::drivers::DRIVERS;
pub use self::context::*;
use log::*;
@ -92,12 +93,12 @@ pub extern fn rust_trap(tf: &mut TrapFrame) {
trace!("Interrupt @ CPU{}: {:?} ", super::cpu::id(), tf.scause.cause());
match tf.scause.cause() {
// M-mode only
Trap::Interrupt(I::MachineExternal) => serial(),
Trap::Interrupt(I::MachineExternal) => external(),
Trap::Interrupt(I::MachineSoft) => ipi(),
Trap::Interrupt(I::MachineTimer) => timer(),
Trap::Exception(E::MachineEnvCall) => sbi(tf),
Trap::Interrupt(I::SupervisorExternal) => serial(),
Trap::Interrupt(I::SupervisorExternal) => external(),
Trap::Interrupt(I::SupervisorSoft) => ipi(),
Trap::Interrupt(I::SupervisorTimer) => timer(),
Trap::Exception(E::IllegalInstruction) => illegal_inst(tf),
@ -116,8 +117,34 @@ fn sbi(tf: &mut TrapFrame) {
tf.sepc += 4;
}
fn serial() {
crate::trap::serial(super::io::getchar());
fn external() {
// true means handled, false otherwise
let handlers = [try_process_serial, try_process_drivers];
for handler in handlers.iter() {
if handler() == true {
break
}
}
}
fn try_process_serial() -> bool {
match super::io::getchar_option() {
Some(ch) => {
crate::trap::serial(ch);
true
}
None => false
}
}
fn try_process_drivers() -> bool {
let mut drivers = DRIVERS.lock();
for driver in drivers.iter_mut() {
if driver.try_handle_interrupt() == true {
return true
}
}
return false
}
fn ipi() {

@ -51,6 +51,14 @@ pub fn getchar() -> char {
}
}
pub fn getchar_option() -> Option<char> {
let c = sbi::console_getchar() as isize;
match c {
-1 => None,
c => Some(c as u8 as char),
}
}
pub fn putfmt(fmt: Arguments) {
SerialPort.write_fmt(fmt).unwrap();
}

@ -7,7 +7,7 @@ use crate::consts::{MEMORY_OFFSET, MEMORY_END, KERN_VA_BASE};
use riscv::register::satp;
#[cfg(feature = "no_mmu")]
pub fn init() {
pub fn init(_dtb: usize) {
init_heap();
let heap_bottom = end as usize;
@ -21,7 +21,7 @@ pub fn init() {
* Init the mermory management module, allow memory access and set up page table and init heap and frame allocator
*/
#[cfg(not(feature = "no_mmu"))]
pub fn init() {
pub fn init(dtb: usize) {
unsafe { sstatus::set_sum(); } // Allow user memory access
// initialize heap and Frame allocator
init_frame_allocator();
@ -29,7 +29,7 @@ pub fn init() {
init_heap();
info!("init_heap end");
// remap the kernel use 4K page
remap_the_kernel();
remap_the_kernel(dtb);
info!("remap_the_kernel end");
}
@ -71,7 +71,7 @@ fn init_frame_allocator() {
/// Remap the kernel memory address with 4K page recorded in p1 page table
#[cfg(not(feature = "no_mmu"))]
fn remap_the_kernel() {
fn remap_the_kernel(dtb: usize) {
let offset = -(super::consts::KERN_VA_BASE as isize);
let mut ms = MemorySet::new_bare();
ms.push(stext as usize, etext as usize, Linear::new(offset, MemoryAttr::default().execute().readonly()), "text");
@ -79,6 +79,7 @@ fn remap_the_kernel() {
ms.push(srodata as usize, erodata as usize, Linear::new(offset, MemoryAttr::default().readonly()), "rodata");
ms.push(bootstack as usize, bootstacktop as usize, Linear::new(offset, MemoryAttr::default()), "stack");
ms.push(sbss as usize, ebss as usize, Linear::new(offset, MemoryAttr::default()), "bss");
ms.push(dtb, dtb + super::consts::MAX_DTB_SIZE, Linear::new(offset, MemoryAttr::default()), "dts");
unsafe { ms.activate(); }
unsafe { SATP = ms.token(); }
mem::forget(ms);

@ -28,8 +28,9 @@ pub extern fn rust_main(hartid: usize, dtb: usize, hart_mask: usize, functions:
crate::logging::init();
interrupt::init();
memory::init();
memory::init(dtb);
timer::init();
crate::drivers::init(dtb);
crate::process::init();
unsafe { cpu::start_others(hart_mask); }

@ -30,3 +30,7 @@ pub fn halt() {
use x86_64::instructions::hlt;
hlt();
}
pub fn fence() {
unsafe { asm!("mfence" ::: "memory"); }
}

@ -0,0 +1 @@
pub mod virtio_mmio;

@ -0,0 +1,147 @@
use core::mem::size_of;
use bitflags::*;
use device_tree::Node;
use device_tree::util::SliceRead;
use log::*;
use rcore_memory::paging::PageTable;
use volatile::{ReadOnly, Volatile, WriteOnly};
use crate::memory::active_table;
use super::super::gpu::virtio_gpu;
use super::super::net::virtio_net;
// virtio 4.2.4 Legacy interface
#[repr(packed)]
#[derive(Debug)]
pub struct VirtIOHeader {
magic: ReadOnly<u32>, // 0x000
version: ReadOnly<u32>, // 0x004
device_id: ReadOnly<u32>, // 0x008
vendor_id: ReadOnly<u32>, // 0x00c
pub device_features: ReadOnly<u32>, // 0x010
pub device_features_sel: WriteOnly<u32>, // 0x014
__r1: [ReadOnly<u32>; 2],
pub driver_features: WriteOnly<u32>, // 0x020
pub driver_features_sel: WriteOnly<u32>, // 0x024
pub guest_page_size: WriteOnly<u32>, // 0x028
__r2: ReadOnly<u32>,
pub queue_sel: WriteOnly<u32>, // 0x030
pub queue_num_max: ReadOnly<u32>, // 0x034
pub queue_num: WriteOnly<u32>, // 0x038
pub queue_align: WriteOnly<u32>, // 0x03c
pub queue_pfn: Volatile<u32>, // 0x040
queue_ready: Volatile<u32>, // new interface only
__r3: [ReadOnly<u32>; 2],
pub queue_notify: WriteOnly<u32>, // 0x050
__r4: [ReadOnly<u32>; 3],
pub interrupt_status: ReadOnly<u32>, // 0x060
pub interrupt_ack: WriteOnly<u32>, // 0x064
__r5: [ReadOnly<u32>; 2],
pub status: Volatile<u32>, // 0x070
__r6: [ReadOnly<u32>; 3],
queue_desc_low: WriteOnly<u32>, // new interface only since here
queue_desc_high: WriteOnly<u32>,
__r7: [ReadOnly<u32>; 2],
queue_avail_low: WriteOnly<u32>,
queue_avail_high: WriteOnly<u32>,
__r8: [ReadOnly<u32>; 2],
queue_used_low: WriteOnly<u32>,
queue_used_high: WriteOnly<u32>,
__r9: [ReadOnly<u32>; 21],
config_generation: ReadOnly<u32>
}
bitflags! {
pub struct VirtIODeviceStatus : u32 {
const ACKNOWLEDGE = 1;
const DRIVER = 2;
const FAILED = 128;
const FEATURES_OK = 8;
const DRIVER_OK = 4;
const DEVICE_NEEDS_RESET = 64;
}
}
#[repr(packed)]
#[derive(Debug)]
pub struct VirtIOVirtqueueDesc {
pub addr: Volatile<u64>,
pub len: Volatile<u32>,
pub flags: Volatile<u16>,
pub next: Volatile<u16>
}
bitflags! {
pub struct VirtIOVirtqueueFlag : u16 {
const NEXT = 1;
const WRITE = 2;
const INDIRECT = 4;
}
}
#[repr(packed)]
#[derive(Debug)]
pub struct VirtIOVirtqueueAvailableRing {
pub flags: Volatile<u16>,
pub idx: Volatile<u16>,
pub ring: [Volatile<u16>; 32], // actual size: queue_size
used_event: Volatile<u16>
}
#[repr(packed)]
#[derive(Debug)]
pub struct VirtIOVirtqueueUsedElem {
id: Volatile<u32>,
len: Volatile<u32>
}
#[repr(packed)]
#[derive(Debug)]
pub struct VirtIOVirtqueueUsedRing {
pub flags: Volatile<u16>,
pub idx: Volatile<u16>,
pub ring: [VirtIOVirtqueueUsedElem; 32], // actual size: queue_size
avail_event: Volatile<u16>
}
// virtio 2.4.2 Legacy Interfaces: A Note on Virtqueue Layout
pub fn virtqueue_size(num: usize, align: usize) -> usize {
(((size_of::<VirtIOVirtqueueDesc>() * num + size_of::<u16>() * (3 + num)) + align) & !(align-1)) +
(((size_of::<u16>() * 3 + size_of::<VirtIOVirtqueueUsedElem>() * num) + align) & !(align-1))
}
pub fn virtqueue_used_elem_offset(num: usize, align: usize) -> usize {
((size_of::<VirtIOVirtqueueDesc>() * num + size_of::<u16>() * (3 + num)) + align) & !(align-1)
}
pub fn virtio_probe(node: &Node) {
if let Some(reg) = node.prop_raw("reg") {
let from = reg.as_slice().read_be_u64(0).unwrap();
let size = reg.as_slice().read_be_u64(8).unwrap();
// assuming one page
active_table().map(from as usize, from as usize);
let mut header = unsafe { &mut *(from as *mut VirtIOHeader) };
let magic = header.magic.read();
let version = header.version.read();
let device_id = header.device_id.read();
// only support legacy device
if magic == 0x74726976 && version == 1 && device_id != 0 { // "virt" magic
info!("Detected virtio net device with vendor id {:#X}", header.vendor_id.read());
info!("Device tree node {:?}", node);
// virtio 3.1.1 Device Initialization
header.status.write(0);
header.status.write(VirtIODeviceStatus::ACKNOWLEDGE.bits());
if device_id == 1 { // net device
virtio_net::virtio_net_init(node);
} else if device_id == 16 { // gpu device
virtio_gpu::virtio_gpu_init(node);
} else {
println!("Unrecognized virtio device {}", device_id);
}
} else {
active_table().unmap(from as usize);
}
}
}

@ -0,0 +1,36 @@
use core::slice;
use device_tree::{DeviceTree, Node};
use super::bus::virtio_mmio::virtio_probe;
const DEVICE_TREE_MAGIC: u32 = 0xd00dfeed;
fn walk_dt_node(dt: &Node) {
if let Ok(compatible) = dt.prop_str("compatible") {
// TODO: query this from table
if compatible == "virtio,mmio" {
virtio_probe(dt);
}
}
for child in dt.children.iter() {
walk_dt_node(child);
}
}
struct DtbHeader {
magic: u32,
size: u32,
}
pub fn init(dtb: usize) {
let header = unsafe {&*(dtb as *const DtbHeader)};
let magic = u32::from_be(header.magic);
if magic == DEVICE_TREE_MAGIC {
let size = u32::from_be(header.size);
let dtb_data = unsafe { slice::from_raw_parts(dtb as *const u8, size as usize) };
if let Ok(dt) = DeviceTree::load(dtb_data) {
walk_dt_node(&dt.root);
}
}
}

@ -0,0 +1 @@
pub mod virtio_gpu;

@ -0,0 +1,477 @@
use alloc::alloc::{GlobalAlloc, Layout};
use alloc::prelude::*;
use core::mem::size_of;
use core::slice;
use bitflags::*;
use device_tree::Node;
use device_tree::util::SliceRead;
use log::*;
use rcore_memory::PAGE_SIZE;
use rcore_memory::paging::PageTable;
use volatile::{ReadOnly, Volatile, WriteOnly};
use crate::arch::cpu;
use crate::HEAP_ALLOCATOR;
use crate::memory::active_table;
use super::super::{DeviceType, Driver, DRIVERS};
use super::super::bus::virtio_mmio::*;
const VIRTIO_GPU_EVENT_DISPLAY : u32 = 1 << 0;
struct VirtIOGpu {
interrupt_parent: u32,
interrupt: u32,
header: usize,
// 0 for transmit, 1 for cursor
queue_num: u32,
queue_address: usize,
queue_page: [usize; 2],
last_used_idx: u16,
frame_buffer: usize,
rect: VirtIOGpuRect
}
#[repr(packed)]
#[derive(Debug)]
struct VirtIOGpuConfig {
events_read: ReadOnly<u32>,
events_clear: WriteOnly<u32>,
num_scanouts: Volatile<u32>
}
bitflags! {
struct VirtIOGpuFeature : u64 {
const VIRGL = 1 << 0;
const EDID = 1 << 1;
// device independent
const NOFIFY_ON_EMPTY = 1 << 24; // legacy
const ANY_LAYOUT = 1 << 27; // legacy
const RING_INDIRECT_DESC = 1 << 28;
const RING_EVENT_IDX = 1 << 29;
const UNUSED = 1 << 30; // legacy
const VERSION_1 = 1 << 32; // detect legacy
const ACCESS_PLATFORM = 1 << 33; // since virtio v1.1
const RING_PACKED = 1 << 34; // since virtio v1.1
const IN_ORDER = 1 << 35; // since virtio v1.1
const ORDER_PLATFORM = 1 << 36; // since virtio v1.1
const SR_IOV = 1 << 37; // since virtio v1.1
const NOTIFICATION_DATA = 1 << 38; // since virtio v1.1
}
}
const VIRTIO_GPU_CMD_GET_DISPLAY_INFO : u32 = 0x100;
const VIRTIO_GPU_CMD_RESOURCE_CREATE_2D : u32 = 0x101;
const VIRTIO_GPU_CMD_RESOURCE_UNREF : u32 = 0x102;
const VIRTIO_GPU_CMD_SET_SCANOUT : u32 = 0x103;
const VIRTIO_GPU_CMD_RESOURCE_FLUSH : u32 = 0x104;
const VIRTIO_GPU_CMD_TRANSFER_TO_HOST_2D : u32 = 0x105;
const VIRTIO_GPU_CMD_RESOURCE_ATTACH_BACKING : u32 = 0x106;
const VIRTIO_GPU_CMD_RESOURCE_DETACH_BACKING : u32 = 0x107;
const VIRTIO_GPU_CMD_GET_CAPSET_INFO : u32 = 0x108;
const VIRTIO_GPU_CMD_GET_CAPSET : u32 = 0x109;
const VIRTIO_GPU_CMD_GET_EDID : u32 = 0x10a;
const VIRTIO_GPU_CMD_UPDATE_CURSOR : u32 = 0x300;
const VIRTIO_GPU_CMD_MOVE_CURSOR : u32 = 0x301;
const VIRTIO_GPU_RESP_OK_NODATA : u32 = 0x1100;
const VIRTIO_GPU_RESP_OK_DISPLAY_INFO : u32 = 0x1101;
const VIRTIO_GPU_RESP_OK_CAPSET_INFO : u32 = 0x1102;
const VIRTIO_GPU_RESP_OK_CAPSET : u32 = 0x1103;
const VIRTIO_GPU_RESP_OK_EDID : u32 = 0x1104;
const VIRTIO_GPU_RESP_ERR_UNSPEC : u32 = 0x1200;
const VIRTIO_GPU_RESP_ERR_OUT_OF_MEMORY : u32 = 0x1201;
const VIRTIO_GPU_RESP_ERR_INVALID_SCANOUT_ID : u32 = 0x1202;
const VIRTIO_GPU_FLAG_FENCE : u32 = 1 << 0;
#[repr(packed)]
#[derive(Debug)]
struct VirtIOGpuCtrlHdr {
hdr_type: u32,
flags: u32,
fence_id: u64,
ctx_id: u32,
padding: u32
}
impl VirtIOGpuCtrlHdr {
fn with_type(hdr_type: u32) -> VirtIOGpuCtrlHdr {
VirtIOGpuCtrlHdr {
hdr_type,
flags: 0,
fence_id: 0,
ctx_id: 0,
padding: 0
}
}
}
#[repr(packed)]
#[derive(Debug, Copy, Clone, Default)]
struct VirtIOGpuRect {
x: u32,
y: u32,
width: u32,
height: u32
}
#[repr(packed)]
#[derive(Debug)]
struct VirtIOGpuRespDisplayInfo {
header: VirtIOGpuCtrlHdr,
rect: VirtIOGpuRect,
enabled: u32,
flags: u32
}
const VIRTIO_GPU_FORMAT_B8G8R8A8_UNORM: u32 = 1;
#[repr(packed)]
#[derive(Debug)]
struct VirtIOGpuResourceCreate2D {
header: VirtIOGpuCtrlHdr,
resource_id: u32,
format: u32,
width: u32,
height: u32
}
#[repr(packed)]
#[derive(Debug)]
struct VirtIOGpuResourceAttachBacking {
header: VirtIOGpuCtrlHdr,
resource_id: u32,
nr_entries: u32, // always 1
addr: u64,
length: u32,
padding: u32
}
#[repr(packed)]
#[derive(Debug)]
struct VirtIOGpuSetScanout {
header: VirtIOGpuCtrlHdr,
rect: VirtIOGpuRect,
scanout_id: u32,
resource_id: u32
}
#[repr(packed)]
#[derive(Debug)]
struct VirtIOGpuTransferToHost2D {
header: VirtIOGpuCtrlHdr,
rect: VirtIOGpuRect,
offset: u64,
resource_id: u32,
padding: u32
}
#[repr(packed)]
#[derive(Debug)]
struct VirtIOGpuResourceFlush {
header: VirtIOGpuCtrlHdr,
rect: VirtIOGpuRect,
resource_id: u32,
padding: u32
}
const VIRTIO_QUEUE_TRANSMIT: usize = 0;
const VIRTIO_QUEUE_RECEIVE: usize = 1;
const VIRTIO_GPU_RESOURCE_ID: u32 = 0xbabe;
impl Driver for VirtIOGpu {
fn try_handle_interrupt(&mut self) -> bool {
// for simplicity
if cpu::id() > 0 {
return false
}
// ensure header page is mapped
active_table().map_if_not_exists(self.header as usize, self.header as usize);
let mut header = unsafe { &mut *(self.header as *mut VirtIOHeader) };
let interrupt = header.interrupt_status.read();
if interrupt != 0 {
header.interrupt_ack.write(interrupt);
debug!("Got interrupt {:?}", interrupt);
let response = unsafe { &mut *(self.queue_page[VIRTIO_QUEUE_RECEIVE] as *mut VirtIOGpuCtrlHdr) };
debug!("response in interrupt: {:?}", response);
return true;
}
return false;
}
fn device_type(&self) -> DeviceType {
DeviceType::Gpu
}
}
fn setup_rings(driver: &mut VirtIOGpu) {
let mut ring = unsafe {
&mut *((driver.queue_address + size_of::<VirtIOVirtqueueDesc>() * driver.queue_num as usize) as *mut VirtIOVirtqueueAvailableRing)
};
// re-add two buffers to desc
// chaining read buffer and write buffer into one desc
for buffer in 0..2 {
let mut desc = unsafe { &mut *(driver.queue_address as *mut VirtIOVirtqueueDesc).add(buffer) };
desc.addr.write(driver.queue_page[buffer] as u64);
desc.len.write(PAGE_SIZE as u32);
if buffer == VIRTIO_QUEUE_TRANSMIT {
// device readable
desc.flags.write(VirtIOVirtqueueFlag::NEXT.bits());
desc.next.write(1);
} else {
// device writable
desc.flags.write(VirtIOVirtqueueFlag::WRITE.bits());
}
ring.ring[buffer].write(0);
}
}
fn notify_device(driver: &mut VirtIOGpu) {
let mut header = unsafe { &mut *(driver.header as *mut VirtIOHeader) };
let mut ring = unsafe {
&mut *((driver.queue_address + size_of::<VirtIOVirtqueueDesc>() * driver.queue_num as usize) as *mut VirtIOVirtqueueAvailableRing)
};
ring.idx.write(ring.idx.read() + 1);
header.queue_notify.write(0);
}
fn setup_framebuffer(driver: &mut VirtIOGpu) {
// get display info
setup_rings(driver);
let mut request_get_display_info = unsafe { &mut *(driver.queue_page[VIRTIO_QUEUE_TRANSMIT] as *mut VirtIOGpuCtrlHdr) };
*request_get_display_info = VirtIOGpuCtrlHdr::with_type(VIRTIO_GPU_CMD_GET_DISPLAY_INFO);
notify_device(driver);
let response_get_display_info = unsafe { &mut *(driver.queue_page[VIRTIO_QUEUE_RECEIVE] as *mut VirtIOGpuRespDisplayInfo) };
info!("response: {:?}", response_get_display_info);
driver.rect = response_get_display_info.rect;
// create resource 2d
setup_rings(driver);
let mut request_resource_create_2d = unsafe { &mut *(driver.queue_page[VIRTIO_QUEUE_TRANSMIT] as *mut VirtIOGpuResourceCreate2D) };
*request_resource_create_2d = VirtIOGpuResourceCreate2D {
header: VirtIOGpuCtrlHdr::with_type(VIRTIO_GPU_CMD_RESOURCE_CREATE_2D),
resource_id: VIRTIO_GPU_RESOURCE_ID,
format: VIRTIO_GPU_FORMAT_B8G8R8A8_UNORM,
width: response_get_display_info.rect.width,
height: response_get_display_info.rect.height
};
notify_device(driver);
let response_resource_create_2d = unsafe { &mut *(driver.queue_page[VIRTIO_QUEUE_RECEIVE] as *mut VirtIOGpuCtrlHdr) };
info!("response: {:?}", response_resource_create_2d);
// alloc continuous pages for the frame buffer
let size = response_get_display_info.rect.width * response_get_display_info.rect.height * 4;
let frame_buffer = unsafe {
HEAP_ALLOCATOR.alloc_zeroed(Layout::from_size_align(size as usize, PAGE_SIZE).unwrap())
} as usize;
mandelbrot(driver.rect.width, driver.rect.height, frame_buffer as *mut u32);
driver.frame_buffer = frame_buffer;
setup_rings(driver);
let mut request_resource_attach_backing = unsafe { &mut *(driver.queue_page[VIRTIO_QUEUE_TRANSMIT] as *mut VirtIOGpuResourceAttachBacking) };
*request_resource_attach_backing = VirtIOGpuResourceAttachBacking {
header: VirtIOGpuCtrlHdr::with_type(VIRTIO_GPU_CMD_RESOURCE_ATTACH_BACKING),
resource_id: VIRTIO_GPU_RESOURCE_ID,
nr_entries: 1,
addr: frame_buffer as u64,
length: size,
padding: 0
};
notify_device(driver);
let response_resource_attach_backing = unsafe { &mut *(driver.queue_page[VIRTIO_QUEUE_RECEIVE] as *mut VirtIOGpuCtrlHdr) };
debug!("response: {:?}", response_resource_attach_backing);
// map frame buffer to screen
setup_rings(driver);
let mut request_set_scanout = unsafe { &mut *(driver.queue_page[VIRTIO_QUEUE_TRANSMIT] as *mut VirtIOGpuSetScanout) };
*request_set_scanout = VirtIOGpuSetScanout {
header: VirtIOGpuCtrlHdr::with_type(VIRTIO_GPU_CMD_SET_SCANOUT),
rect: response_get_display_info.rect,
scanout_id: 0,
resource_id: VIRTIO_GPU_RESOURCE_ID
};
notify_device(driver);
let response_set_scanout = unsafe { &mut *(driver.queue_page[VIRTIO_QUEUE_RECEIVE] as *mut VirtIOGpuCtrlHdr) };
info!("response: {:?}", response_set_scanout);
flush_frame_buffer_to_screen(driver);
}
// from Wikipedia
fn hsv_to_rgb(h: u32, s: f32, v: f32) -> (f32, f32, f32) {
let hi = (h / 60) % 6;
let f = (h % 60) as f32 / 60.0;
let p = v * (1.0 - s);
let q = v * (1.0 - f * s);
let t = v * (1.0 - (1.0 - f) * s);
match hi {
0 => (v, t, p),
1 => (q, v, p),
2 => (p, v, t),
3 => (p, q, v),
4 => (t, p, v),
5 => (v, p, q),
_ => unreachable!()
}
}
fn mandelbrot(width: u32, height: u32, frame_buffer: *mut u32) {
let size = width * height * 4;
let frame_buffer_data = unsafe {
slice::from_raw_parts_mut(frame_buffer as *mut u32, (size / 4) as usize)
};
for x in 0..width {
for y in 0..height {
let index = y * width + x;
let scale = 5e-3;
let xx = (x as f32 - width as f32 / 2.0) * scale;
let yy = (y as f32 - height as f32 / 2.0) * scale;
let mut re = xx as f32;
let mut im = yy as f32;
let mut iter: u32 = 0;
loop {
iter = iter + 1;
let new_re = re * re - im * im + xx as f32;
let new_im = re * im * 2.0 + yy as f32;
if new_re * new_re + new_im * new_im > 1e3 {
break;
}
re = new_re;
im = new_im;
if iter == 60 {
break;
}
}
iter = iter * 6;
let (r, g, b) = hsv_to_rgb(iter, 1.0, 0.5);
let rr = (r * 256.0) as u32;
let gg = (g * 256.0) as u32;
let bb = (b * 256.0) as u32;
let color = (bb << 16) | (gg << 8) | rr;
frame_buffer_data[index as usize] = color;
}
println!("working on x {}/{}", x, width);
}
}
fn flush_frame_buffer_to_screen(driver: &mut VirtIOGpu) {
// copy data from guest to host
setup_rings(driver);
let mut request_transfer_to_host_2d = unsafe { &mut *(driver.queue_page[VIRTIO_QUEUE_TRANSMIT] as *mut VirtIOGpuTransferToHost2D) };
*request_transfer_to_host_2d = VirtIOGpuTransferToHost2D {
header: VirtIOGpuCtrlHdr::with_type(VIRTIO_GPU_CMD_TRANSFER_TO_HOST_2D),
rect: driver.rect,
offset: 0,
resource_id: VIRTIO_GPU_RESOURCE_ID,
padding: 0
};
notify_device(driver);
let response_transfer_to_host_2d = unsafe { &mut *(driver.queue_page[VIRTIO_QUEUE_RECEIVE] as *mut VirtIOGpuCtrlHdr) };
info!("response: {:?}", response_transfer_to_host_2d);
// flush data to screen
setup_rings(driver);
let mut request_resource_flush = unsafe { &mut *(driver.queue_page[VIRTIO_QUEUE_TRANSMIT] as *mut VirtIOGpuResourceFlush) };
*request_resource_flush = VirtIOGpuResourceFlush {
header: VirtIOGpuCtrlHdr::with_type(VIRTIO_GPU_CMD_RESOURCE_FLUSH),
rect: driver.rect,
resource_id: VIRTIO_GPU_RESOURCE_ID,
padding: 0
};
notify_device(driver);
let response_resource_flush = unsafe { &mut *(driver.queue_page[VIRTIO_QUEUE_RECEIVE] as *mut VirtIOGpuCtrlHdr) };
info!("response: {:?}", response_resource_flush);
}
pub fn virtio_gpu_init(node: &Node) {
let reg = node.prop_raw("reg").unwrap();
let from = reg.as_slice().read_be_u64(0).unwrap();
let mut header = unsafe { &mut *(from as *mut VirtIOHeader) };
header.status.write(VirtIODeviceStatus::DRIVER.bits());
let mut device_features_bits: u64;
header.device_features_sel.write(0); // device features [0, 32)
device_features_bits = header.device_features.read().into();
header.device_features_sel.write(1); // device features [32, 64)
device_features_bits = device_features_bits + ((header.device_features.read() as u64) << 32);
let device_features = VirtIOGpuFeature::from_bits_truncate(device_features_bits);
info!("Device features {:?}", device_features);
// negotiate these flags only
let supported_features = VirtIOGpuFeature::empty();
let driver_features = (device_features & supported_features).bits();
header.driver_features_sel.write(0); // driver features [0, 32)
header.driver_features.write((driver_features & 0xFFFFFFFF) as u32);
header.driver_features_sel.write(1); // driver features [32, 64)
header.driver_features.write(((driver_features & 0xFFFFFFFF00000000) >> 32) as u32);
// read configuration space
let mut config = unsafe { &mut *((from + 0x100) as *mut VirtIOGpuConfig) };
info!("Config: {:?}", config);
// virtio 4.2.4 Legacy interface
// configure two virtqueues: ingress and egress
header.guest_page_size.write(PAGE_SIZE as u32); // one page
let queue_num = 2;
let mut driver = VirtIOGpu {
interrupt: node.prop_u32("interrupts").unwrap(),
interrupt_parent: node.prop_u32("interrupt-parent").unwrap(),
header: from as usize,
queue_num,
queue_address: 0,
queue_page: [0, 0],
last_used_idx: 0,
frame_buffer: 0,
rect: VirtIOGpuRect::default()
};
// 0 for control, 1 for cursor, we use controlq only
for queue in 0..2 {
header.queue_sel.write(queue);
assert_eq!(header.queue_pfn.read(), 0); // not in use
// 0 for transmit, 1 for receive
let queue_num_max = header.queue_num_max.read();
assert!(queue_num_max >= queue_num); // queue available
let size = virtqueue_size(queue_num as usize, PAGE_SIZE);
assert!(size % PAGE_SIZE == 0);
// alloc continuous pages
let address = unsafe {
HEAP_ALLOCATOR.alloc_zeroed(Layout::from_size_align(size, PAGE_SIZE).unwrap())
} as usize;
debug!("queue {} using page address {:#X} with size {}", queue, address as usize, size);
header.queue_num.write(queue_num);
header.queue_align.write(PAGE_SIZE as u32);
header.queue_pfn.write((address as u32) >> 12);
if queue == 0 {
driver.queue_address = address;
// 0 for transmit, 1 for receive
for buffer in 0..2 {
// allocate a page for each buffer
let page = unsafe {
HEAP_ALLOCATOR.alloc_zeroed(Layout::from_size_align(PAGE_SIZE, PAGE_SIZE).unwrap())
} as usize;
driver.queue_page[buffer as usize] = page;
debug!("buffer {} using page address {:#X}", buffer, page as usize);
}
}
header.queue_notify.write(queue);
}
header.status.write(VirtIODeviceStatus::DRIVER_OK.bits());
setup_framebuffer(&mut driver);
DRIVERS.lock().push(Box::new(driver));
}

@ -0,0 +1,55 @@
use alloc::prelude::*;
use core::any::Any;
use lazy_static::lazy_static;
use smoltcp::wire::EthernetAddress;
use crate::sync::SpinNoIrqLock;
mod device_tree;
pub mod bus;
pub mod net;
mod gpu;
pub enum DeviceType {
Net,
Gpu
}
pub trait Driver : Send {
// if interrupt belongs to this driver, handle it and return true
// return false otherwise
fn try_handle_interrupt(&mut self) -> bool;
// return the correspondent device type, see DeviceType
fn device_type(&self) -> DeviceType;
}
pub trait NetDriver: Driver + AsAny {
// get mac address for this device
fn get_mac(&self) -> EthernetAddress;
// get interface name for this device
fn get_ifname(&self) -> String;
}
// little hack, see https://users.rust-lang.org/t/how-to-downcast-from-a-trait-any-to-a-struct/11219/3
pub trait AsAny {
fn as_any(&self) -> &Any;
}
impl<T: Any> AsAny for T {
fn as_any(&self) -> &Any { self }
}
lazy_static! {
pub static ref DRIVERS: SpinNoIrqLock<Vec<Box<Driver>>> = SpinNoIrqLock::new(Vec::new());
}
lazy_static! {
pub static ref NET_DRIVERS: SpinNoIrqLock<Vec<Box<NetDriver>>> = SpinNoIrqLock::new(Vec::new());
}
pub fn init(dtb: usize) {
device_tree::init(dtb);
}

@ -0,0 +1 @@
pub mod virtio_net;

@ -0,0 +1,403 @@
use alloc::alloc::{GlobalAlloc, Layout};
use alloc::format;
use alloc::prelude::*;
use alloc::sync::Arc;
use core::mem::size_of;
use core::slice;
use bitflags::*;
use device_tree::Node;
use device_tree::util::SliceRead;
use log::*;
use rcore_memory::PAGE_SIZE;
use rcore_memory::paging::PageTable;
use smoltcp::phy::{self, DeviceCapabilities};
use smoltcp::Result;
use smoltcp::time::Instant;
use smoltcp::wire::EthernetAddress;
use volatile::{ReadOnly, Volatile};
use crate::arch::cpu;
use crate::HEAP_ALLOCATOR;
use crate::memory::active_table;
use crate::sync::SpinNoIrqLock as Mutex;
use super::super::{DeviceType, Driver, DRIVERS, NET_DRIVERS, NetDriver};
use super::super::bus::virtio_mmio::*;
pub struct VirtIONet {
interrupt_parent: u32,
interrupt: u32,
header: usize,
mac: EthernetAddress,
queue_num: u32,
// 0 for receive, 1 for transmit
queue_address: [usize; 2],
queue_page: [usize; 2],
last_used_idx: [u16; 2],
}
#[derive(Clone)]
pub struct VirtIONetDriver(Arc<Mutex<VirtIONet>>);
const VIRTIO_QUEUE_RECEIVE: usize = 0;
const VIRTIO_QUEUE_TRANSMIT: usize = 1;
impl Driver for VirtIONetDriver {
fn try_handle_interrupt(&mut self) -> bool {
// for simplicity
if cpu::id() > 0 {
return false
}
let mut driver = self.0.lock();
// ensure header page is mapped
active_table().map_if_not_exists(driver.header as usize, driver.header as usize);
let mut header = unsafe { &mut *(driver.header as *mut VirtIOHeader) };
let interrupt = header.interrupt_status.read();
if interrupt != 0 {
header.interrupt_ack.write(interrupt);
let interrupt_status = VirtIONetworkInterruptStatus::from_bits_truncate(interrupt);
debug!("Got interrupt {:?}", interrupt_status);
if interrupt_status.contains(VirtIONetworkInterruptStatus::USED_RING_UPDATE) {
// need to change when queue_num is larger than one
let queue = VIRTIO_QUEUE_TRANSMIT;
let used_ring_offset = virtqueue_used_elem_offset(driver.queue_num as usize, PAGE_SIZE);
let mut used_ring = unsafe {
&mut *((driver.queue_address[queue] + used_ring_offset) as *mut VirtIOVirtqueueUsedRing)
};
if driver.last_used_idx[queue] < used_ring.idx.read() {
assert_eq!(driver.last_used_idx[queue], used_ring.idx.read() - 1);
info!("Processing queue {} from {} to {}", queue, driver.last_used_idx[queue], used_ring.idx.read());
driver.last_used_idx[queue] = used_ring.idx.read();
}
} else if interrupt_status.contains(VirtIONetworkInterruptStatus::CONFIGURATION_CHANGE) {
// TODO: update mac and status
unimplemented!("virtio-net configuration change not implemented");
}
return true;
} else {
return false;
}
}
fn device_type(&self) -> DeviceType {
DeviceType::Net
}
}
impl VirtIONet {
fn transmit_available(&self) -> bool {
let used_ring_offset = virtqueue_used_elem_offset(self.queue_num as usize, PAGE_SIZE);
let mut used_ring = unsafe {
&mut *((self.queue_address[VIRTIO_QUEUE_TRANSMIT] + used_ring_offset) as *mut VirtIOVirtqueueUsedRing)
};
let result = self.last_used_idx[VIRTIO_QUEUE_TRANSMIT] == used_ring.idx.read();
result
}
fn receive_available(&self) -> bool {
let used_ring_offset = virtqueue_used_elem_offset(self.queue_num as usize, PAGE_SIZE);
let mut used_ring = unsafe {
&mut *((self.queue_address[VIRTIO_QUEUE_RECEIVE] + used_ring_offset) as *mut VirtIOVirtqueueUsedRing)
};
let result = self.last_used_idx[VIRTIO_QUEUE_RECEIVE] < used_ring.idx.read();
result
}
}
impl NetDriver for VirtIONetDriver {
fn get_mac(&self) -> EthernetAddress {
self.0.lock().mac
}
fn get_ifname(&self) -> String {
format!("virtio{}", self.0.lock().interrupt)
}
}
pub struct VirtIONetRxToken(VirtIONetDriver);
pub struct VirtIONetTxToken(VirtIONetDriver);
impl<'a> phy::Device<'a> for VirtIONetDriver {
type RxToken = VirtIONetRxToken;
type TxToken = VirtIONetTxToken;
fn receive(&'a mut self) -> Option<(Self::RxToken, Self::TxToken)> {
let driver = self.0.lock();
if driver.transmit_available() && driver.receive_available() {
// ugly borrow rules bypass
Some((VirtIONetRxToken(self.clone()),
VirtIONetTxToken(self.clone())))
} else {
None
}
}
fn transmit(&'a mut self) -> Option<Self::TxToken> {
let driver = self.0.lock();
if driver.transmit_available() {
Some(VirtIONetTxToken(self.clone()))
} else {
None
}
}
fn capabilities(&self) -> DeviceCapabilities {
let mut caps = DeviceCapabilities::default();
caps.max_transmission_unit = 1536;
caps.max_burst_size = Some(1);
caps
}
}
impl phy::RxToken for VirtIONetRxToken {
fn consume<R, F>(self, timestamp: Instant, f: F) -> Result<R>
where F: FnOnce(&[u8]) -> Result<R>
{
let buffer = {
let mut driver = (self.0).0.lock();
// ensure header page is mapped
active_table().map_if_not_exists(driver.header as usize, driver.header as usize);
let mut header = unsafe { &mut *(driver.header as *mut VirtIOHeader) };
let used_ring_offset = virtqueue_used_elem_offset(driver.queue_num as usize, PAGE_SIZE);
let mut used_ring = unsafe {
&mut *((driver.queue_address[VIRTIO_QUEUE_RECEIVE] + used_ring_offset) as *mut VirtIOVirtqueueUsedRing)
};
assert!(driver.last_used_idx[VIRTIO_QUEUE_RECEIVE] == used_ring.idx.read() - 1);
driver.last_used_idx[VIRTIO_QUEUE_RECEIVE] = used_ring.idx.read();
let mut payload = unsafe { slice::from_raw_parts_mut((driver.queue_page[VIRTIO_QUEUE_RECEIVE] + size_of::<VirtIONetHeader>()) as *mut u8, PAGE_SIZE - 10)};
let buffer = payload.to_vec();
for i in 0..(PAGE_SIZE - size_of::<VirtIONetHeader>()) {
payload[i] = 0;
}
let mut ring = unsafe {
&mut *((driver.queue_address[VIRTIO_QUEUE_RECEIVE] + size_of::<VirtIOVirtqueueDesc>() * driver.queue_num as usize) as *mut VirtIOVirtqueueAvailableRing)
};
ring.idx.write(ring.idx.read() + 1);
header.queue_notify.write(VIRTIO_QUEUE_RECEIVE as u32);
buffer
};
f(&buffer)
}
}
impl phy::TxToken for VirtIONetTxToken {
fn consume<R, F>(self, _timestamp: Instant, len: usize, f: F) -> Result<R>
where F: FnOnce(&mut [u8]) -> Result<R>,
{
let mut driver = (self.0).0.lock();
// ensure header page is mapped
active_table().map_if_not_exists(driver.header as usize, driver.header as usize);
let mut header = unsafe { &mut *(driver.header as *mut VirtIOHeader) };
let payload_target = unsafe { slice::from_raw_parts_mut((driver.queue_page[VIRTIO_QUEUE_TRANSMIT] + size_of::<VirtIONetHeader>()) as *mut u8, len)};
let result = f(payload_target);
let mut net_header = unsafe { &mut *(driver.queue_page[VIRTIO_QUEUE_TRANSMIT] as *mut VirtIONetHeader) };
let mut header = unsafe { &mut *(driver.header as *mut VirtIOHeader) };
let mut ring = unsafe {
&mut *((driver.queue_address[VIRTIO_QUEUE_TRANSMIT] + size_of::<VirtIOVirtqueueDesc>() * driver.queue_num as usize) as *mut VirtIOVirtqueueAvailableRing)
};
// re-add buffer to desc
let mut desc = unsafe { &mut *(driver.queue_address[VIRTIO_QUEUE_TRANSMIT] as *mut VirtIOVirtqueueDesc) };
desc.addr.write(driver.queue_page[VIRTIO_QUEUE_TRANSMIT] as u64);
desc.len.write((len + size_of::<VirtIONetHeader>()) as u32);
// memory barrier
crate::arch::cpu::fence();
// add desc to available ring
ring.idx.write(ring.idx.read() + 1);
header.queue_notify.write(VIRTIO_QUEUE_TRANSMIT as u32);
result
}
}
bitflags! {
struct VirtIONetFeature : u64 {
const CSUM = 1 << 0;
const GUEST_CSUM = 1 << 1;
const CTRL_GUEST_OFFLOADS = 1 << 2;
const MTU = 1 << 3;
const MAC = 1 << 5;
const GSO = 1 << 6;
const GUEST_TSO4 = 1 << 7;
const GUEST_TSO6 = 1 << 8;
const GUEST_ECN = 1 << 9;
const GUEST_UFO = 1 << 10;
const HOST_TSO4 = 1 << 11;
const HOST_TSO6 = 1 << 12;
const HOST_ECN = 1 << 13;
const HOST_UFO = 1 << 14;
const MRG_RXBUF = 1 << 15;
const STATUS = 1 << 16;
const CTRL_VQ = 1 << 17;
const CTRL_RX = 1 << 18;
const CTRL_VLAN = 1 << 19;
const CTRL_RX_EXTRA = 1 << 20;
const GUEST_ANNOUNCE = 1 << 21;
const MQ = 1 << 22;
const CTL_MAC_ADDR = 1 << 23;
// device independent
const RING_INDIRECT_DESC = 1 << 28;
const RING_EVENT_IDX = 1 << 29;
const VERSION_1 = 1 << 32; // legacy
}
}
bitflags! {
struct VirtIONetworkStatus : u16 {
const LINK_UP = 1;
const ANNOUNCE = 2;
}
}
bitflags! {
struct VirtIONetworkInterruptStatus : u32 {
const USED_RING_UPDATE = 1 << 0;
const CONFIGURATION_CHANGE = 1 << 1;
}
}
#[repr(packed)]
#[derive(Debug)]
struct VirtIONetworkConfig {
mac: [u8; 6],
status: ReadOnly<u16>
}
// virtio 5.1.6 Device Operation
#[repr(packed)]
#[derive(Debug)]
struct VirtIONetHeader {
flags: Volatile<u8>,
gso_type: Volatile<u8>,
hdr_len: Volatile<u16>, // cannot rely on this
gso_size: Volatile<u16>,
csum_start: Volatile<u16>,
csum_offset: Volatile<u16>,
// payload starts from here
}
pub fn virtio_net_init(node: &Node) {
let reg = node.prop_raw("reg").unwrap();
let from = reg.as_slice().read_be_u64(0).unwrap();
let mut header = unsafe { &mut *(from as *mut VirtIOHeader) };
header.status.write(VirtIODeviceStatus::DRIVER.bits());
let mut device_features_bits: u64;
header.device_features_sel.write(0); // device features [0, 32)
device_features_bits = header.device_features.read().into();
header.device_features_sel.write(1); // device features [32, 64)
device_features_bits = device_features_bits + ((header.device_features.read() as u64) << 32);
let device_features = VirtIONetFeature::from_bits_truncate(device_features_bits);
debug!("Device features {:?}", device_features);
// negotiate these flags only
let supported_features = VirtIONetFeature::MAC | VirtIONetFeature::STATUS;
let driver_features = (device_features & supported_features).bits();
header.driver_features_sel.write(0); // driver features [0, 32)
header.driver_features.write((driver_features & 0xFFFFFFFF) as u32);
header.driver_features_sel.write(1); // driver features [32, 64)
header.driver_features.write(((driver_features & 0xFFFFFFFF00000000) >> 32) as u32);
// read configuration space
let mut mac: [u8; 6];
let mut status: VirtIONetworkStatus;
let mut config = unsafe { &mut *((from + 0x100) as *mut VirtIONetworkConfig) };
mac = config.mac;
status = VirtIONetworkStatus::from_bits_truncate(config.status.read());
debug!("Got MAC address {:?} and status {:?}", mac, status);
// virtio 4.2.4 Legacy interface
// configure two virtqueues: ingress and egress
header.guest_page_size.write(PAGE_SIZE as u32); // one page
let queue_num = 1; // for simplicity
let mut driver = VirtIONet {
interrupt: node.prop_u32("interrupts").unwrap(),
interrupt_parent: node.prop_u32("interrupt-parent").unwrap(),
header: from as usize,
mac: EthernetAddress(mac),
queue_num: queue_num,
queue_address: [0, 0],
queue_page: [0, 0],
last_used_idx: [0, 0],
};
// 0 for receive, 1 for transmit
for queue in 0..2 {
header.queue_sel.write(queue as u32);
assert_eq!(header.queue_pfn.read(), 0); // not in use
let queue_num_max = header.queue_num_max.read();
assert!(queue_num_max >= queue_num); // queue available
let size = virtqueue_size(queue_num as usize, PAGE_SIZE);
assert!(size % PAGE_SIZE == 0);
// alloc continuous pages
let address = unsafe {
HEAP_ALLOCATOR.alloc_zeroed(Layout::from_size_align(size, PAGE_SIZE).unwrap())
} as usize;
driver.queue_address[queue] = address;
debug!("queue {} using page address {:#X} with size {}", queue, address as usize, size);
header.queue_num.write(queue_num);
header.queue_align.write(PAGE_SIZE as u32);
header.queue_pfn.write((address as u32) >> 12);
// allocate a page for buffer
let page = unsafe {
HEAP_ALLOCATOR.alloc_zeroed(Layout::from_size_align(PAGE_SIZE, PAGE_SIZE).unwrap())
} as usize;
driver.queue_page[queue] = page;
// fill first desc
let mut desc = unsafe { &mut *(address as *mut VirtIOVirtqueueDesc) };
desc.addr.write(page as u64);
desc.len.write(PAGE_SIZE as u32);
if queue == VIRTIO_QUEUE_RECEIVE {
// device writable
desc.flags.write(VirtIOVirtqueueFlag::WRITE.bits());
} else if queue == VIRTIO_QUEUE_TRANSMIT {
// driver readable
desc.flags.write(0);
}
// memory barrier
crate::arch::cpu::fence();
if queue == VIRTIO_QUEUE_RECEIVE {
// add the desc to the ring
let mut ring = unsafe {
&mut *((address + size_of::<VirtIOVirtqueueDesc>() * queue_num as usize) as *mut VirtIOVirtqueueAvailableRing)
};
ring.ring[0].write(0);
// wait for first packet
ring.idx.write(ring.idx.read() + 1);
}
// notify device about the new buffer
header.queue_notify.write(queue as u32);
debug!("queue {} using page address {:#X}", queue, page);
}
header.status.write(VirtIODeviceStatus::DRIVER_OK.bits());
let mut net_driver = VirtIONetDriver(Arc::new(Mutex::new(driver)));
DRIVERS.lock().push(Box::new(net_driver.clone()));
NET_DRIVERS.lock().push(Box::new(net_driver));
}

@ -28,6 +28,8 @@ mod fs;
mod sync;
mod trap;
mod shell;
mod drivers;
mod net;
mod backtrace;
#[allow(dead_code)]

@ -0,0 +1,2 @@
mod test;
pub use self::test::server;

@ -0,0 +1,91 @@
use crate::thread;
use crate::drivers::NET_DRIVERS;
use smoltcp::wire::*;
use smoltcp::iface::*;
use smoltcp::socket::*;
use alloc::collections::BTreeMap;
use crate::drivers::NetDriver;
use crate::drivers::net::virtio_net::VirtIONetDriver;
use alloc::vec;
use smoltcp::time::Instant;
use core::fmt::Write;
pub extern fn server(_arg: usize) -> ! {
if NET_DRIVERS.lock().len() < 1 {
loop {
thread::yield_now();
}
}
let mut driver = {
let ref_driver = &mut *NET_DRIVERS.lock()[0];
ref_driver.as_any().downcast_ref::<VirtIONetDriver>().unwrap().clone()
};
let ethernet_addr = driver.get_mac();
let ip_addrs = [IpCidr::new(IpAddress::v4(10,0,0,2), 24)];
let neighbor_cache = NeighborCache::new(BTreeMap::new());
let mut iface = EthernetInterfaceBuilder::new(driver.clone())
.ethernet_addr(ethernet_addr)
.ip_addrs(ip_addrs)
.neighbor_cache(neighbor_cache)
.finalize();
let udp_rx_buffer = UdpSocketBuffer::new(vec![UdpPacketMetadata::EMPTY], vec![0; 64]);
let udp_tx_buffer = UdpSocketBuffer::new(vec![UdpPacketMetadata::EMPTY], vec![0; 128]);
let udp_socket = UdpSocket::new(udp_rx_buffer, udp_tx_buffer);
let tcp_rx_buffer = TcpSocketBuffer::new(vec![0; 1024]);
let tcp_tx_buffer = TcpSocketBuffer::new(vec![0; 1024]);
let tcp_socket = TcpSocket::new(tcp_rx_buffer, tcp_tx_buffer);
let mut sockets = SocketSet::new(vec![]);
let udp_handle = sockets.add(udp_socket);
let tcp_handle = sockets.add(tcp_socket);
loop {
{
let timestamp = Instant::from_millis(unsafe { crate::trap::TICK as i64 });
match iface.poll(&mut sockets, timestamp) {
Ok(_) => {},
Err(e) => {
println!("poll error: {}", e);
}
}
// udp server
{
let mut socket = sockets.get::<UdpSocket>(udp_handle);
if !socket.is_open() {
socket.bind(6969).unwrap();
}
let client = match socket.recv() {
Ok((data, endpoint)) => {
Some(endpoint)
}
Err(_) => None
};
if let Some(endpoint) = client {
let hello = b"hello\n";
socket.send_slice(hello, endpoint).unwrap();
}
}
// simple http server
{
let mut socket = sockets.get::<TcpSocket>(tcp_handle);
if !socket.is_open() {
socket.listen(80).unwrap();
}
if socket.can_send() {
write!(socket, "HTTP/1.1 200 OK\r\nServer: rCore\r\nContent-Length: 13\r\nContent-Type: text/html\r\nConnection: Closed\r\n\r\nHello, world!\r\n").unwrap();
socket.close();
}
}
}
thread::yield_now();
}
}
Loading…
Cancel
Save