From 047f4ffdccda66ebc093db735137395fa1481512 Mon Sep 17 00:00:00 2001 From: WangRunji Date: Wed, 23 Jan 2019 22:11:41 +0800 Subject: [PATCH] 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 --- crate/memory/src/paging/mod.rs | 10 + kernel/Cargo.lock | 41 +++ kernel/Cargo.toml | 2 + kernel/Makefile | 22 +- kernel/src/arch/aarch64/cpu.rs | 6 +- kernel/src/arch/riscv32/consts.rs | 2 + kernel/src/arch/riscv32/cpu.rs | 6 +- kernel/src/arch/riscv32/interrupt.rs | 35 +- kernel/src/arch/riscv32/io.rs | 8 + kernel/src/arch/riscv32/memory.rs | 9 +- kernel/src/arch/riscv32/mod.rs | 3 +- kernel/src/arch/x86_64/cpu.rs | 6 +- kernel/src/drivers/bus/mod.rs | 1 + kernel/src/drivers/bus/virtio_mmio.rs | 147 ++++++++ kernel/src/drivers/device_tree.rs | 36 ++ kernel/src/drivers/gpu/mod.rs | 1 + kernel/src/drivers/gpu/virtio_gpu.rs | 477 ++++++++++++++++++++++++++ kernel/src/drivers/mod.rs | 55 +++ kernel/src/drivers/net/mod.rs | 1 + kernel/src/drivers/net/virtio_net.rs | 403 ++++++++++++++++++++++ kernel/src/lib.rs | 2 + kernel/src/net/mod.rs | 2 + kernel/src/net/test.rs | 91 +++++ 23 files changed, 1352 insertions(+), 14 deletions(-) create mode 100644 kernel/src/drivers/bus/mod.rs create mode 100644 kernel/src/drivers/bus/virtio_mmio.rs create mode 100644 kernel/src/drivers/device_tree.rs create mode 100644 kernel/src/drivers/gpu/mod.rs create mode 100644 kernel/src/drivers/gpu/virtio_gpu.rs create mode 100644 kernel/src/drivers/mod.rs create mode 100644 kernel/src/drivers/net/mod.rs create mode 100644 kernel/src/drivers/net/virtio_net.rs create mode 100644 kernel/src/net/mod.rs create mode 100644 kernel/src/net/test.rs diff --git a/crate/memory/src/paging/mod.rs b/crate/memory/src/paging/mod.rs index e8679ed..e663747 100644 --- a/crate/memory/src/paging/mod.rs +++ b/crate/memory/src/paging/mod.rs @@ -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 diff --git a/kernel/Cargo.lock b/kernel/Cargo.lock index 2ea4fa0..0d41c34 100644 --- a/kernel/Cargo.lock +++ b/kernel/Cargo.lock @@ -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)" = "" +"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)" = "" "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)" = "" "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" diff --git a/kernel/Cargo.toml b/kernel/Cargo.toml index be10d48..66a24ec 100644 --- a/kernel/Cargo.toml +++ b/kernel/Cargo.toml @@ -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" } diff --git a/kernel/Makefile b/kernel/Makefile index 6014788..b344fcb 100644 --- a/kernel/Makefile +++ b/kernel/Makefile @@ -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 diff --git a/kernel/src/arch/aarch64/cpu.rs b/kernel/src/arch/aarch64/cpu.rs index 8dd916f..701150d 100644 --- a/kernel/src/arch/aarch64/cpu.rs +++ b/kernel/src/arch/aarch64/cpu.rs @@ -5,4 +5,8 @@ pub fn halt() { pub fn id() -> usize { // TODO: cpu id 0 -} \ No newline at end of file +} + +pub fn fence() { + unsafe { asm!("dmb ish" ::: "memory"); } +} diff --git a/kernel/src/arch/riscv32/consts.rs b/kernel/src/arch/riscv32/consts.rs index d6f6cd7..6a8728d 100644 --- a/kernel/src/arch/riscv32/consts.rs +++ b/kernel/src/arch/riscv32/consts.rs @@ -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; \ No newline at end of file diff --git a/kernel/src/arch/riscv32/cpu.rs b/kernel/src/arch/riscv32/cpu.rs index f34f8fc..b42f447 100644 --- a/kernel/src/arch/riscv32/cpu.rs +++ b/kernel/src/arch/riscv32/cpu.rs @@ -34,4 +34,8 @@ pub unsafe fn start_others(hart_mask: usize) { pub fn halt() { unsafe { riscv::asm::wfi() } -} \ No newline at end of file +} + +pub fn fence() { + unsafe { asm!("fence" ::: "memory"); } +} diff --git a/kernel/src/arch/riscv32/interrupt.rs b/kernel/src/arch/riscv32/interrupt.rs index dea3b27..1c2f2ae 100644 --- a/kernel/src/arch/riscv32/interrupt.rs +++ b/kernel/src/arch/riscv32/interrupt.rs @@ -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() { diff --git a/kernel/src/arch/riscv32/io.rs b/kernel/src/arch/riscv32/io.rs index 04b7822..a6ef9e0 100644 --- a/kernel/src/arch/riscv32/io.rs +++ b/kernel/src/arch/riscv32/io.rs @@ -51,6 +51,14 @@ pub fn getchar() -> char { } } +pub fn getchar_option() -> Option { + 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(); } diff --git a/kernel/src/arch/riscv32/memory.rs b/kernel/src/arch/riscv32/memory.rs index 0ebfbdc..d71de6d 100644 --- a/kernel/src/arch/riscv32/memory.rs +++ b/kernel/src/arch/riscv32/memory.rs @@ -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); diff --git a/kernel/src/arch/riscv32/mod.rs b/kernel/src/arch/riscv32/mod.rs index 24d4350..d6525d3 100644 --- a/kernel/src/arch/riscv32/mod.rs +++ b/kernel/src/arch/riscv32/mod.rs @@ -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); } diff --git a/kernel/src/arch/x86_64/cpu.rs b/kernel/src/arch/x86_64/cpu.rs index 85a1a97..6a1ff41 100644 --- a/kernel/src/arch/x86_64/cpu.rs +++ b/kernel/src/arch/x86_64/cpu.rs @@ -29,4 +29,8 @@ pub fn init() { pub fn halt() { use x86_64::instructions::hlt; hlt(); -} \ No newline at end of file +} + +pub fn fence() { + unsafe { asm!("mfence" ::: "memory"); } +} diff --git a/kernel/src/drivers/bus/mod.rs b/kernel/src/drivers/bus/mod.rs new file mode 100644 index 0000000..08d5c84 --- /dev/null +++ b/kernel/src/drivers/bus/mod.rs @@ -0,0 +1 @@ +pub mod virtio_mmio; \ No newline at end of file diff --git a/kernel/src/drivers/bus/virtio_mmio.rs b/kernel/src/drivers/bus/virtio_mmio.rs new file mode 100644 index 0000000..a93ef35 --- /dev/null +++ b/kernel/src/drivers/bus/virtio_mmio.rs @@ -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, // 0x000 + version: ReadOnly, // 0x004 + device_id: ReadOnly, // 0x008 + vendor_id: ReadOnly, // 0x00c + pub device_features: ReadOnly, // 0x010 + pub device_features_sel: WriteOnly, // 0x014 + __r1: [ReadOnly; 2], + pub driver_features: WriteOnly, // 0x020 + pub driver_features_sel: WriteOnly, // 0x024 + pub guest_page_size: WriteOnly, // 0x028 + __r2: ReadOnly, + pub queue_sel: WriteOnly, // 0x030 + pub queue_num_max: ReadOnly, // 0x034 + pub queue_num: WriteOnly, // 0x038 + pub queue_align: WriteOnly, // 0x03c + pub queue_pfn: Volatile, // 0x040 + queue_ready: Volatile, // new interface only + __r3: [ReadOnly; 2], + pub queue_notify: WriteOnly, // 0x050 + __r4: [ReadOnly; 3], + pub interrupt_status: ReadOnly, // 0x060 + pub interrupt_ack: WriteOnly, // 0x064 + __r5: [ReadOnly; 2], + pub status: Volatile, // 0x070 + __r6: [ReadOnly; 3], + queue_desc_low: WriteOnly, // new interface only since here + queue_desc_high: WriteOnly, + __r7: [ReadOnly; 2], + queue_avail_low: WriteOnly, + queue_avail_high: WriteOnly, + __r8: [ReadOnly; 2], + queue_used_low: WriteOnly, + queue_used_high: WriteOnly, + __r9: [ReadOnly; 21], + config_generation: ReadOnly +} + +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, + pub len: Volatile, + pub flags: Volatile, + pub next: Volatile +} + +bitflags! { + pub struct VirtIOVirtqueueFlag : u16 { + const NEXT = 1; + const WRITE = 2; + const INDIRECT = 4; + } +} + +#[repr(packed)] +#[derive(Debug)] +pub struct VirtIOVirtqueueAvailableRing { + pub flags: Volatile, + pub idx: Volatile, + pub ring: [Volatile; 32], // actual size: queue_size + used_event: Volatile +} + +#[repr(packed)] +#[derive(Debug)] +pub struct VirtIOVirtqueueUsedElem { + id: Volatile, + len: Volatile +} + +#[repr(packed)] +#[derive(Debug)] +pub struct VirtIOVirtqueueUsedRing { + pub flags: Volatile, + pub idx: Volatile, + pub ring: [VirtIOVirtqueueUsedElem; 32], // actual size: queue_size + avail_event: Volatile +} + +// virtio 2.4.2 Legacy Interfaces: A Note on Virtqueue Layout +pub fn virtqueue_size(num: usize, align: usize) -> usize { + (((size_of::() * num + size_of::() * (3 + num)) + align) & !(align-1)) + + (((size_of::() * 3 + size_of::() * num) + align) & !(align-1)) +} + +pub fn virtqueue_used_elem_offset(num: usize, align: usize) -> usize { + ((size_of::() * num + size_of::() * (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); + } + } +} \ No newline at end of file diff --git a/kernel/src/drivers/device_tree.rs b/kernel/src/drivers/device_tree.rs new file mode 100644 index 0000000..5aa7b05 --- /dev/null +++ b/kernel/src/drivers/device_tree.rs @@ -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); + } + } +} diff --git a/kernel/src/drivers/gpu/mod.rs b/kernel/src/drivers/gpu/mod.rs new file mode 100644 index 0000000..0d8a41d --- /dev/null +++ b/kernel/src/drivers/gpu/mod.rs @@ -0,0 +1 @@ +pub mod virtio_gpu; \ No newline at end of file diff --git a/kernel/src/drivers/gpu/virtio_gpu.rs b/kernel/src/drivers/gpu/virtio_gpu.rs new file mode 100644 index 0000000..86ecb0a --- /dev/null +++ b/kernel/src/drivers/gpu/virtio_gpu.rs @@ -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, + events_clear: WriteOnly, + num_scanouts: Volatile +} + +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::() * 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::() * 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)); +} \ No newline at end of file diff --git a/kernel/src/drivers/mod.rs b/kernel/src/drivers/mod.rs new file mode 100644 index 0000000..a56271a --- /dev/null +++ b/kernel/src/drivers/mod.rs @@ -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 AsAny for T { + fn as_any(&self) -> &Any { self } +} + +lazy_static! { + pub static ref DRIVERS: SpinNoIrqLock>> = SpinNoIrqLock::new(Vec::new()); +} + +lazy_static! { + pub static ref NET_DRIVERS: SpinNoIrqLock>> = SpinNoIrqLock::new(Vec::new()); +} + +pub fn init(dtb: usize) { + device_tree::init(dtb); +} \ No newline at end of file diff --git a/kernel/src/drivers/net/mod.rs b/kernel/src/drivers/net/mod.rs new file mode 100644 index 0000000..8203b06 --- /dev/null +++ b/kernel/src/drivers/net/mod.rs @@ -0,0 +1 @@ +pub mod virtio_net; \ No newline at end of file diff --git a/kernel/src/drivers/net/virtio_net.rs b/kernel/src/drivers/net/virtio_net.rs new file mode 100644 index 0000000..69b2b3c --- /dev/null +++ b/kernel/src/drivers/net/virtio_net.rs @@ -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>); + +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 { + 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(self, timestamp: Instant, f: F) -> Result + where F: FnOnce(&[u8]) -> Result + { + 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::()) as *mut u8, PAGE_SIZE - 10)}; + let buffer = payload.to_vec(); + for i in 0..(PAGE_SIZE - size_of::()) { + payload[i] = 0; + } + + let mut ring = unsafe { + &mut *((driver.queue_address[VIRTIO_QUEUE_RECEIVE] + size_of::() * 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(self, _timestamp: Instant, len: usize, f: F) -> Result + where F: FnOnce(&mut [u8]) -> Result, + { + 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::()) 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::() * 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::()) 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 +} + +// virtio 5.1.6 Device Operation +#[repr(packed)] +#[derive(Debug)] +struct VirtIONetHeader { + flags: Volatile, + gso_type: Volatile, + hdr_len: Volatile, // cannot rely on this + gso_size: Volatile, + csum_start: Volatile, + csum_offset: Volatile, + // 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::() * 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)); +} \ No newline at end of file diff --git a/kernel/src/lib.rs b/kernel/src/lib.rs index 29e5ce0..53a0129 100644 --- a/kernel/src/lib.rs +++ b/kernel/src/lib.rs @@ -28,6 +28,8 @@ mod fs; mod sync; mod trap; mod shell; +mod drivers; +mod net; mod backtrace; #[allow(dead_code)] diff --git a/kernel/src/net/mod.rs b/kernel/src/net/mod.rs new file mode 100644 index 0000000..aff647a --- /dev/null +++ b/kernel/src/net/mod.rs @@ -0,0 +1,2 @@ +mod test; +pub use self::test::server; \ No newline at end of file diff --git a/kernel/src/net/test.rs b/kernel/src/net/test.rs new file mode 100644 index 0000000..dbc5530 --- /dev/null +++ b/kernel/src/net/test.rs @@ -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::().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::(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::(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(); + } + +}