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(); + } + +}