diff --git a/Cargo.lock b/Cargo.lock index deda1c1ec..eed5a796d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -210,6 +210,16 @@ dependencies = [ "typenum 1.10.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "getset" +version = "0.0.6" +source = "git+https://github.com/razican/getset?branch=deref#f237bdcc82d39c08ef8f9c841d028908ca2843d5" +dependencies = [ + "proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 0.15.32 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "gif" version = "0.10.0" @@ -267,6 +277,14 @@ dependencies = [ "spin 0.5.0 (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" @@ -280,6 +298,11 @@ name = "lzw" version = "0.10.0" source = "git+https://github.com/SunriseOS/lzw#b4ca11f83315129ee683aa3d9ca8d6c3a4cde1b4" +[[package]] +name = "managed" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "maplit" version = "1.0.1" @@ -439,6 +462,17 @@ name = "smallvec" version = "0.6.10" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "smoltcp" +version = "0.5.0" +source = "git+https://github.com/roblabla/smoltcp#21b0b27f8b5d3b5bf56fbd9cc5356d13422529e3" +dependencies = [ + "bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "byteorder 1.3.2 (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" @@ -563,9 +597,11 @@ dependencies = [ "arrayvec 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)", "bit_field 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", "bitfield 0.13.1 (registry+https://github.com/rust-lang/crates.io-index)", + "bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", "failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", "font-rs 0.1.3 (git+https://github.com/SunriseOS/font-rs)", + "getset 0.0.6 (git+https://github.com/razican/getset?branch=deref)", "hashbrown 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "linked_list_allocator 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)", @@ -618,6 +654,20 @@ dependencies = [ "sunrise-libutils 0.1.0", ] +[[package]] +name = "sunrise-virtio" +version = "0.1.0" +dependencies = [ + "bit_field 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "bitfield 0.13.1 (registry+https://github.com/rust-lang/crates.io-index)", + "bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", + "smoltcp 0.5.0 (git+https://github.com/roblabla/smoltcp)", + "static_assertions 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", + "sunrise-libuser 0.1.0", +] + [[package]] name = "swipc-gen" version = "0.1.0" @@ -771,6 +821,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum font-rs 0.1.3 (git+https://github.com/SunriseOS/font-rs)" = "" "checksum gcc 0.3.55 (registry+https://github.com/rust-lang/crates.io-index)" = "8f5f3913fa0bfe7ee1fd8248b6b9f42a5af4b9d65ec2dd2c3c26132b950ecfc2" "checksum generic-array 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ef25c5683767570c2bbd7deba372926a55eaae9982d7726ee2a1050239d45b9d" +"checksum getset 0.0.6 (git+https://github.com/razican/getset?branch=deref)" = "" "checksum gif 0.10.0 (git+https://github.com/SunriseOS/image-gif)" = "" "checksum hashbrown 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "3bae29b6653b3412c2e71e9d486db9f9df5d701941d86683005efb9f2d28e3da" "checksum hashbrown 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e1de41fb8dba9714efd92241565cdff73f78508c95697dd56787d3cba27e2353" @@ -779,8 +830,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bc5729f27f159ddd61f4df6228e827e86643d4d3e7c32183cb30a1c08f604a14" "checksum libc 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)" = "6281b86796ba5e4366000be6e9e18bf35580adf9e63fbe2294aadb587613a319" "checksum linked_list_allocator 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)" = "47314ec1d29aa869ee7cb5a5be57be9b1055c56567d59c3fb6689926743e0bea" +"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 lzw 0.10.0 (git+https://github.com/SunriseOS/lzw)" = "" +"checksum managed 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "fdcec5e97041c7f0f1c5b7d93f12e57293c831c646f4cc7a5db59460c7ea8de6" "checksum maplit 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "08cbb6b4fef96b6d77bfc40ec491b1690c779e77b05cd9f07f787ed376fd4c43" "checksum mashup 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "f2d82b34c7fb11bb41719465c060589e291d505ca4735ea30016a91f6fc79c3b" "checksum mashup-impl 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "aa607bfb674b4efb310512527d64266b065de3f894fc52f84efcbf7eaa5965fb" @@ -802,6 +855,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum scopeguard 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "94258f53601af11e6a49f722422f6e3425c52b06245a5cf9bc09908b174f5e27" "checksum sha-1 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "51b9d1f3b5de8a167ab06834a7c883bd197f2191e1dda1a22d9ccfeedbf9aded" "checksum smallvec 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)" = "ab606a9c5e214920bb66c458cd7be8ef094f813f20fe77a54cc7dbfff220d4b7" +"checksum smoltcp 0.5.0 (git+https://github.com/roblabla/smoltcp)" = "" "checksum spin 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)" = "ceac490aa12c567115b40b7b7fceca03a6c9d53d5defea066123debc83c5dc1f" "checksum spin 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "44363f6f51401c34e7be73db0db371c04705d35efbe9f7d6082e03a921a32c55" "checksum static_assertions 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "b4f8de36da215253eb5f24020bfaa0646613b48bf7ebe36cdfa37c3b3b33b241" diff --git a/Cargo.toml b/Cargo.toml index 36a272531..f2c848db0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [workspace] -members = ["kernel", "bootstrap", "shell", "libuser", "clock", "sm", "vi", "ahci", "libutils", "libkern", "swipc-gen", "swipc-parser"] +members = ["kernel", "bootstrap", "shell", "libuser", "clock", "sm", "vi", "ahci", "libutils", "libkern", "swipc-gen", "swipc-parser", "virtio"] [profile.release] debug = true diff --git a/Makefile.toml b/Makefile.toml index 9c9580809..73df426cf 100644 --- a/Makefile.toml +++ b/Makefile.toml @@ -138,15 +138,30 @@ dependencies = ["install-rust-src"] command = "cargo" args = ["xbuild", "--target=i386-unknown-none-user", "--package=sunrise-ahci", "--release"] +[tasks.virtio] +workspace = false +description = "Compiles sunrise-virtio" +dependencies = ["install-rust-src"] +command = "cargo" +args = ["xbuild", "--target=i386-unknown-none-user", "--package=sunrise-virtio"] + +[tasks.virtio-release] +workspace = false +description = "Compiles sunrise-virtio" +dependencies = ["install-rust-src"] +command = "cargo" +args = ["xbuild", "--target=i386-unknown-none-user", "--package=sunrise-virtio", "--release"] + + [tasks.userspace] workspace = false description = "Compiles userspace apps" -dependencies = ["shell", "clock", "sm", "vi", "ahci"] +dependencies = ["shell", "clock", "sm", "vi", "ahci", "virtio"] [tasks.userspace-release] workspace = false description = "Compiles userspace apps" -dependencies = ["shell-release", "clock-release", "sm-release", "vi-release", "ahci-release"] +dependencies = ["shell-release", "clock-release", "sm-release", "vi-release", "ahci-release", "virtio-release"] [tasks.iso] workspace = false @@ -162,6 +177,7 @@ cp target/i386-unknown-none-user/debug/sunrise-clock isofiles/boot/ cp target/i386-unknown-none-user/debug/sunrise-sm isofiles/boot/ cp target/i386-unknown-none-user/debug/sunrise-vi isofiles/boot/ cp target/i386-unknown-none-user/debug/sunrise-ahci isofiles/boot/ +cp target/i386-unknown-none-user/debug/sunrise-virtio isofiles/boot/ mkisofs-rs external/grub/isofiles isofiles -o os.iso -b boot/grub/i386-pc/eltorito.img --no-emul-boot --boot-info-table --embedded-boot external/grub/embedded.img ''' ] @@ -180,6 +196,7 @@ cp target/i386-unknown-none-user/release/sunrise-clock isofiles/boot/ cp target/i386-unknown-none-user/release/sunrise-sm isofiles/boot/ cp target/i386-unknown-none-user/release/sunrise-vi isofiles/boot/ cp target/i386-unknown-none-user/release/sunrise-ahci isofiles/boot/ +cp target/i386-unknown-none-user/release/sunrise-virtio isofiles/boot/ mkisofs-rs external/grub/isofiles isofiles -o os.iso -b boot/grub/i386-pc/eltorito.img --no-emul-boot --boot-info-table --embedded-boot external/grub/embedded.img ''' ] @@ -213,13 +230,28 @@ description = "Runs the bootable ISO in qemu." dependencies = ["iso-release", "create-disk-if-not-exist"] command = "qemu-system-i386" args = [ + # Boot device "-cdrom", "os.iso", - "-serial", "stdio", + # Redirect serial to STDIO + "-serial", "mon:stdio", + # Redirect graphics to VNC "-vnc", "${VNC_PORT}", + # Don't reboot on triple fault "-no-reboot", - "-enable-kvm", + # Enable hardware acceleration + #"-enable-kvm", + # Setup an AHCI device, with a disk on the first IDE port. "-drive", "id=diskA,file=DISK.img,format=raw,if=none", "-device", "ahci,id=ahci", "-device", "ide-drive,drive=diskA,bus=ahci.0", "-machine", "q35", + # Setup a network device, using virtio-net. + # TODO: iommu_platform=true + "-device", "virtio-net,disable-legacy=on,netdev=u1", "-netdev", "user,id=u1", + # Dump packets going on the vlan + "-object", "filter-dump,id=f1,netdev=u1,file=dump.dat", + # Trace virtio-related events + "--trace", "virtio_set_status,file=trace", "--trace", "virtio_queue_notify,file=trace", "--trace", "virtio_notify,file=trace", "--trace", "virtio_input_queue_full,file=trace", + "--trace", "virtio_net_announce_notify,file=trace", "--trace", "virtio_net_announce_timer,file=trace", "--trace", "virtio_net_handle_announce,file=trace", + "--trace", "virtio_net_post_load_device,file=trace", "--trace", "virtio_notify_irqfd,file=trace", ] [tasks.qemu-debug] @@ -228,14 +260,32 @@ description = "Runs the bootable ISO in qemu with gdb support" dependencies = ["iso", "create-disk-if-not-exist"] command = "qemu-system-i386" args = [ + # Boot device "-cdrom", "os.iso", - "-serial", "stdio", + # Redirect serial to STDIO + "-serial", "mon:stdio", + # Redirect graphics to VNC "-vnc", "${VNC_PORT}", - "-no-reboot", + # Enable hardware acceleration + #"-enable-kvm", + # Setup an AHCI device, with a disk on the first IDE port. + "-drive", "id=diskA,file=DISK.img,format=raw,if=none", "-device", "ahci,id=ahci", "-device", "ide-drive,drive=diskA,bus=ahci.0", + # Setup a network device, using virtio-net. + # TODO: ,iommu_platform=true + "-device", "virtio-net,disable-legacy=on,netdev=u1", "-netdev", "user,id=u1", + # Dump packets going on the vlan + "-object", "filter-dump,id=f1,netdev=u1,file=dump.dat", + + # Enable GDB "-gdb", "tcp::${GDB_PORT}", "-S", + # Print state on CPU reset "-d", "cpu_reset", "-drive", "id=diskA,file=DISK.img,format=raw,if=none", "-device", "ahci,id=ahci", "-device", "ide-drive,drive=diskA,bus=ahci.0", "-machine", "q35", + # Trace virtio-related events + "--trace", "virtio_set_status,file=trace", "--trace", "virtio_queue_notify,file=trace", "--trace", "virtio_notify,file=trace", "--trace", "virtio_input_queue_full,file=trace", + "--trace", "virtio_net_announce_notify,file=trace", "--trace", "virtio_net_announce_timer,file=trace", "--trace", "virtio_net_handle_announce,file=trace", + "--trace", "virtio_net_post_load_device,file=trace", "--trace", "virtio_notify_irqfd,file=trace", ] [tasks.doc] @@ -274,7 +324,7 @@ args = ["-c", "kernel/src/main.rs", "bootstrap/src/main.rs", "shell/src/main.rs", "libuser/src/lib.rs", "clock/src/main.rs", "sm/src/main.rs", "vi/src/main.rs", "ahci/src/main.rs", "libutils/src/lib.rs", "libkern/src/lib.rs", "swipc-gen/src/lib.rs", - "swipc-parser/src/lib.rs" + "swipc-parser/src/lib.rs", "virtio/src/main.rs", ] [tasks.clippy-sunrise-target] diff --git a/ahci/src/main.rs b/ahci/src/main.rs index dc842f40b..28bbc8832 100644 --- a/ahci/src/main.rs +++ b/ahci/src/main.rs @@ -64,7 +64,6 @@ extern crate log; #[macro_use] extern crate bitfield; -mod pci; mod hba; mod fis; mod disk; @@ -80,6 +79,8 @@ use spin::Mutex; use sunrise_libuser::syscalls; use sunrise_libuser::ipc::server::SessionWrapper; use sunrise_libuser::ahci::{AhciInterface as IAhciInterface, IDiskProxy, IDisk as _}; +use sunrise_libuser::pci; +use sunrise_libuser::pci::{PciHeader, BAR}; /// Array of discovered disk. /// @@ -101,7 +102,20 @@ static DISKS: Mutex>>> = Mutex::new(Vec::new()); /// 3. Start the event loop. fn main() { debug!("AHCI driver starting up"); - let ahci_controllers = pci::get_ahci_controllers(); + let ahci_controllers = pci::discover() + .filter(|device| device.class() == 0x01 && device.subclass() == 0x06 && device.prog_if() == 0x01) + .map(|device| { + match device.header() { + PciHeader::GeneralDevice(header00) => { + match header00.bar(5) { + Ok(BAR::Memory(memory)) => (memory.phys_addr(), memory.size()), + _ => panic!("PCI device with unexpected BAR 5") + } + }, + _ => panic!("PCI device with unexpected header") + } + }); + debug!("AHCI controllers : {:#x?}", ahci_controllers); for (bar5, _) in ahci_controllers { DISKS.lock().extend( diff --git a/ahci/src/pci.rs b/ahci/src/pci.rs deleted file mode 100644 index 1f14f5e9a..000000000 --- a/ahci/src/pci.rs +++ /dev/null @@ -1,329 +0,0 @@ -//! PCI discovery -//! -//! A minimal PCI implementation, that permits only discovering AHCI devices, and querying their BAR. - -use sunrise_libutils::io::{Io, Pio}; -use spin::Mutex; -use alloc::vec::Vec; - -/// The CONFIG_ADDRESS I/O location. -pub const CONFIG_ADDRESS: u16 = 0xCF8; -/// The CONFIG_DATA I/O location. -pub const CONFIG_DATA: u16 = 0xCFC; - -/// A struct tying the two pci config ports together. -struct PciConfigPortsPair { - /// The address port. - /// - /// Write the '''address''' of the config-space register you want to access. - /// - /// An address is formatted as follow: - /// - /// * 31 Enable bit - /// * 30:24 Reserved - /// * 23:16 Bus Number - /// * 15:11 Device Number - /// * 10:8 Function Number - /// * 7:0 Register Offset - address: Pio, - /// The data port. - /// - /// After having put the address of the register you want in `.address`, - /// read this port to retrieve its value. - data: Pio -} - -/// A mutex around the two ports used to address pci configuration space. -static PCI_CONFIG_PORTS: Mutex = Mutex::new(PciConfigPortsPair { - address: Pio::new(CONFIG_ADDRESS), - data: Pio::new(CONFIG_DATA) -}); - -/// The highest addressable bus. -const MAX_BUS: u8 = 255; -/// The highest addressable slot on a bus. -const MAX_SLOT: u8 = 31; -/// The highest addressable function on a slot on a bus. -const MAX_FUNC: u8 = 15; -/// The highest addressable register on a function on a slot on a bus. -const MAX_REGISTER: u8 = 63; - -/// A pci device, addressed by its bus number, slot, and function. -#[derive(Debug, Copy, Clone)] -#[allow(clippy::missing_docs_in_private_items)] -struct PciDevice { - /// The device's bus number. - bus: u8, - /// The device's slot number on its bus. - slot: u8, - /// The device's function number. - function: u8, - - /* [register 0x00] */ - /// Device id. - did: u16, - /// Vendor id. - vid: u16, - /* [register 0x01] */ - /* status + command are volatile */ - /* [register 0x02] */ - class: u8, - subclass: u8, - prog_if: u8, - rev_id: u8, - /* [register 0x03] */ - /* bist is volatile */ - header_type: u8, - latency_timer: u8, - cache_line_size: u8, - - /// Remaining registers values, based on header type. - header: PciHeader -} - -/// Pci header when Header Type == 0x00 (General device). -#[derive(Copy, Clone, Debug)] -#[allow(clippy::missing_docs_in_private_items)] -struct PciHeader00 { - bar0: BAR, - bar1: BAR, - bar2: BAR, - bar3: BAR, - bar4: BAR, - bar5: BAR, - cardbus_cis_ptr: u32, - subsystem_id: u16, - subsystem_vendor_id: u16, - expansion_rom_base_address: u32, - capabilities_ptr: u8, - max_latency: u8, - min_grant: u8, - interrupt_pin: u8, - interrupt_line: u8, -} - -/// Contents of pci config registers 0x4-0xf, structure varies based on Header Type. -#[derive(Copy, Clone, Debug)] -enum PciHeader { - /// header type == 0x00 - GeneralDevice(PciHeader00), - /// header type == 0x01, not implemented - PCItoPCIBridge, - /// header type == 0x02, not implemented - CardBus, - /// header type == other - UnknownHeaderType(u8) -} - -/// Base Address Registers. Minimal implementation, does not support 64-bits BARs. -#[derive(Copy, Clone, Debug)] -enum BAR { - /// a memory space address and its size - Memory(u32, u32), - /// an IO space address - Io(u32, u32) -} - -impl PciDevice { - /// Checks if a device exists on given bus>slot>function. - /// - /// This is done by reading the Device ID - Vendor ID register (register 0). - /// If `0xFFFF_FFFF` is read back, this means that the device was non-existent, and we return None. - #[allow(clippy::absurd_extreme_comparisons)] - fn probe(bus: u8, slot: u8, function: u8) -> Option { - debug_assert!(bus <= MAX_BUS); - debug_assert!(slot <= MAX_SLOT); - debug_assert!(function <= MAX_FUNC); - let did_vid = pci_config_read_word(bus, slot, function, 0); - return if did_vid == 0xFFFF_FFFF { - None - } else { - Some(Self { - bus, - slot, - function, - did: (did_vid >> 16) as u16, - vid: did_vid as u16, - class: (pci_config_read_word(bus, slot, function, 2) >> 24) as u8, - subclass: (pci_config_read_word(bus, slot, function, 2) >> 16) as u8, - prog_if: (pci_config_read_word(bus, slot, function, 2) >> 8) as u8, - rev_id: pci_config_read_word(bus, slot, function, 2) as u8, - header_type: (pci_config_read_word(bus, slot, function, 3) >> 16) as u8, - latency_timer: (pci_config_read_word(bus, slot, function, 3) >> 8) as u8, - cache_line_size: pci_config_read_word(bus, slot, function, 3) as u8, - - header: read_header(bus, slot, function) - }) - }; - - /// Reads the remaining of the pci-registers, organising them based on header type. - fn read_header(bus: u8, slot: u8, function: u8) -> PciHeader { - // header_type, but bit 8 informs if device is multi-function, ignore it. - let header_type = (pci_config_read_word(bus, slot, function, 3) >> 16) as u8; - return match header_type & 0x7f { - 0x00 => PciHeader::GeneralDevice(PciHeader00 { - bar0: decode_bar(bus, slot, function, 4), - bar1: decode_bar(bus, slot, function, 5), - bar2: decode_bar(bus, slot, function, 6), - bar3: decode_bar(bus, slot, function, 7), - bar4: decode_bar(bus, slot, function, 8), - bar5: decode_bar(bus, slot, function, 9), - cardbus_cis_ptr: pci_config_read_word(bus, slot, function, 0xa), - subsystem_id: (pci_config_read_word(bus, slot, function, 0xb) >> 16) as u16, - subsystem_vendor_id: pci_config_read_word(bus, slot, function, 0xb) as u16, - expansion_rom_base_address: pci_config_read_word(bus, slot, function, 0xc), - capabilities_ptr: pci_config_read_word(bus, slot, function, 0xd) as u8, - max_latency: (pci_config_read_word(bus, slot, function, 0xf) >> 24) as u8, - min_grant: (pci_config_read_word(bus, slot, function, 0xf) >> 16) as u8, - interrupt_pin: (pci_config_read_word(bus, slot, function, 0xf) >> 8) as u8, - interrupt_line: pci_config_read_word(bus, slot, function, 0xf) as u8, - }), - 0x01 => PciHeader::PCItoPCIBridge, - 0x02 => PciHeader::CardBus, - other => PciHeader::UnknownHeaderType(other) - }; - - /// Decode an u32 to BAR. - /// 64-bit BARs are not supported. - fn decode_bar(bus: u8, slot: u8, function: u8, register: u8) -> BAR { - // read bar address - let addr = pci_config_read_word(bus, slot, function, register); - // write to get length - pci_config_write_word(bus, slot, function, register, 0xFFFF_FFFF); - // read back length - let length = pci_config_read_word(bus, slot, function, register); - // restore original value - pci_config_write_word(bus, slot, function, register, addr); - match addr & 0x01 { - 0 => { - // memory space bar - BAR::Memory(addr & 0xFFFF_FFF0, (!(length & 0xFFFF_FFF0)).wrapping_add(1)) - }, - _ => { - // io space bar - BAR::Io(addr & 0xFFFF_FFFC, (!(length & 0xFFFF_FFFC)).wrapping_add(1)) - } - } - } - } - } - - /// Reads a configuration space register. - fn read_config_register(&self, register: u8) -> u32 { - pci_config_read_word(self.bus, self.slot, self.function, register) - } - - /// Writes to a configuration space register. - fn write_config_register(&self, register: u8, value: u32) { - pci_config_write_word(self.bus, self.slot, self.function, register, value) - } - - // register 1 - - /// Reads the status register. - fn status(&self) -> u16 { - (self.read_config_register(1) >> 16) as u16 - } - - /// Reads the command register. - fn command(&self) -> u16 { - (self.read_config_register(1) >> 0) as u16 - } -} - -/// Read one of the 64 32-bit registers of a pci bus>device>func. -#[allow(clippy::absurd_extreme_comparisons)] -fn pci_config_read_word(bus: u8, slot: u8, func: u8, register: u8) -> u32 { - debug_assert!(bus <= MAX_BUS); - debug_assert!(slot <= MAX_SLOT); - debug_assert!(func <= MAX_FUNC); - debug_assert!(register <= MAX_REGISTER); - let lbus = u32::from(bus); - let lslot = u32::from(slot); - let lfunc = u32::from(func); - let lregister = u32::from(register); - let mut ports = PCI_CONFIG_PORTS.lock(); - - /* create the configuration address */ - let address: u32 = (lbus << 16) | (lslot << 11) | - (lfunc << 8) | (lregister << 2) | 0x80000000; - - /* write out the address */ - ports.address.write(address); - - /* read the data */ - ports.data.read() -} - -/// Read one of the 64 32-bit registers of a pci bus>device>func. -#[allow(clippy::absurd_extreme_comparisons)] -fn pci_config_write_word(bus: u8, slot: u8, func: u8, register: u8, value: u32) { - debug_assert!(bus <= MAX_BUS); - debug_assert!(slot <= MAX_SLOT); - debug_assert!(func <= MAX_FUNC); - debug_assert!(register <= MAX_REGISTER); - let lbus = u32::from(bus); - let lslot = u32::from(slot); - let lfunc = u32::from(func); - let lregister = u32::from(register); - let mut ports = PCI_CONFIG_PORTS.lock(); - - /* create the configuration address */ - let address: u32 = (lbus << 16) | (lslot << 11) | - (lfunc << 8) | (lregister << 2) | 0x80000000; - - /* write out the address */ - ports.address.write(address); - - /* read the data */ - ports.data.write(value) -} - -/// Discover all pci devices, by probing the PID-VID of every slot on every bus. -/// -/// A device is discovered when its `(bus, slot, function 0x00)[register 0x00] != 0xFFFF_FFFF`. -/// Then, an additional [PciDevice] will be returned for every of its other functions that also -/// return anything different from `0xFFFF_FFFF`. -fn discover() -> Vec { - let mut devices = vec![]; - for bus in 0..MAX_BUS { - for slot in 0..MAX_SLOT { - // test function 0. - if let Some(device) = PciDevice::probe(bus, slot, 0) { - let is_multifunction = device.header_type & 0x80 != 0; - devices.push(device); - // check for other function on the same device - if is_multifunction { - for function in 1..MAX_FUNC { - if let Some(device) = PciDevice::probe(bus, slot, function) { - devices.push(device); - } - } - } - } - } - } - devices -} - -/// Gets the ahci controllers found by pci discovery. -/// -/// # Returns -/// -/// Returns the controller's BAR5 address and size, if one was found. -pub fn get_ahci_controllers() -> Vec<(u32, u32)> { - discover().iter() - .filter(|device| device.class == 0x01 && device.subclass == 0x06 && device.prog_if == 0x01) - .map(|device| { - match device.header { - PciHeader::GeneralDevice(header00) => { - match header00.bar5 { - BAR::Memory(addr, size) => (addr, size), - _ => panic!("PCI device with unexpected BAR 5") - } - }, - _ => panic!("PCI device with unexpected header") - } - }) - .collect() -} diff --git a/isofiles/boot/grub/grub.cfg b/isofiles/boot/grub/grub.cfg index a57ef6d78..207487988 100644 --- a/isofiles/boot/grub/grub.cfg +++ b/isofiles/boot/grub/grub.cfg @@ -26,5 +26,6 @@ menuentry "my os" { module2 /boot/sunrise-sm sm module2 /boot/sunrise-vi vi module2 /boot/sunrise-ahci ahci + module2 /boot/sunrise-virtio virtio boot } diff --git a/kernel/src/devices/hpet.rs b/kernel/src/devices/hpet.rs index 43683eaac..018580a99 100644 --- a/kernel/src/devices/hpet.rs +++ b/kernel/src/devices/hpet.rs @@ -108,7 +108,8 @@ pub struct HpetRegister { /// General Interrupt Status Register. pub general_interrupt_status: Mmio, // 0x20 _reserved3: [u8; 0xCC], // 0x24 - /// main counter value. + /// Current value of the main counter. Should be an up-counter updating at + /// the frequency specified by the period field. pub main_counter_value: Mmio, // 0xF0 _reserved4: u64, // 0xF8 } @@ -361,7 +362,7 @@ impl Hpet { /// Return the frequency of the HPET device. pub fn get_frequency(&self) -> u64 { - 1000000000000000 / u64::from(self.get_period()) + 1_000_000_000_000_000 / u64::from(self.get_period()) } /// Enable the "legacy mapping". @@ -528,8 +529,24 @@ pub unsafe fn init(hpet: &acpi::Hpet) -> bool { // Clear the interrupt state hpet_instance.enable(); - timer::set_kernel_timer_info(16, hpet_instance.get_frequency(), irq_period_ns); + timer::set_kernel_timer_info(16, hpet_instance.get_frequency(), irq_period_ns, get_tick); HPET_INSTANCE = Some(hpet_instance); true } + +/// Returns the current tick in nanoseconds +fn get_tick() -> u64 { + unsafe { + let instance = HPET_INSTANCE.as_ref().expect("HPET not initialized!"); + let period = instance.get_period(); + if period < 1_000_000 { + // nanosecond precision + (*instance.inner).main_counter_value.read() / (1_000_000 / u64::from(instance.get_period())) + } else { + // 100-nanosecond precision. This is the worse possible precision on + // the HPET. + (*instance.inner).main_counter_value.read() / (100_000_000 / u64::from(instance.get_period())) * 100 + } + } +} \ No newline at end of file diff --git a/kernel/src/devices/pit.rs b/kernel/src/devices/pit.rs index 1ee01b36a..0cfb270a8 100644 --- a/kernel/src/devices/pit.rs +++ b/kernel/src/devices/pit.rs @@ -56,6 +56,7 @@ use crate::sync::SpinLock; use crate::io::Io; use crate::i386::pio::Pio; use crate::timer; +use core::convert::TryFrom; /// The oscillator frequency when not divided, in hertz. const OSCILLATOR_FREQ: usize = 1193182; @@ -206,7 +207,7 @@ pub unsafe fn init_channel_0() { ); ports.write_reload_value(ChannelSelector::Channel0, CHAN_0_DIVISOR); - timer::set_kernel_timer_info(0, OSCILLATOR_FREQ as u64, 1_000_000_000 / (CHAN_0_FREQUENCY as u64)); + timer::set_kernel_timer_info(0, OSCILLATOR_FREQ as u64, 1_000_000_000 / (CHAN_0_FREQUENCY as u64), get_tick); } /// Prevent the PIT from generating interrupts. @@ -215,3 +216,11 @@ pub unsafe fn disable() { ports.port_cmd.write(0b00110010); // channel 0, lobyte/hibyte, one-shot ports.write_reload_value(ChannelSelector::Channel0, 1); } + +/// Get the current tick in nanosecond. +/// +/// Note that the PIT's frequency is set at 10 millisecond, so the update +/// frequency of this tick is going to be, erm, not ideal. +fn get_tick() -> u64 { + u64::try_from(crate::event::get_current_count(0)).unwrap().wrapping_mul(100_000) +} \ No newline at end of file diff --git a/kernel/src/event.rs b/kernel/src/event.rs index 29b72cfe4..fb1010516 100644 --- a/kernel/src/event.rs +++ b/kernel/src/event.rs @@ -159,7 +159,7 @@ impl Waitable for IRQEvent { fn register(&self) { let curproc = scheduler::get_current_thread(); let mut veclock = self.state.waiting_processes.lock(); - info!("Registering {:010x} for irq {}", &*curproc as *const _ as usize, self.state.irqnum); + debug!("Registering {:010x} for irq {}", &*curproc as *const _ as usize, self.state.irqnum); if veclock.iter().find(|v| Arc::ptr_eq(&curproc, v)).is_none() { veclock.push(scheduler::get_current_thread()); } @@ -187,6 +187,12 @@ pub fn wait_event(irq: u8) -> IRQEvent { } } +/// Gets the number of times a certain IRQ got triggered since boot. +pub fn get_current_count(irqnum: u8) -> usize { + IRQ_STATES[usize::from(irqnum)].counter.load(Ordering::SeqCst) +} + + /// Global state of an IRQ. /// /// Counts the number of times this IRQ was triggered from kernel boot. @@ -214,10 +220,11 @@ impl IRQState { } /// Global state for all the IRQ handled by the IOAPIC. -static IRQ_STATES: [IRQState; 17] = [ +static IRQ_STATES: [IRQState; 24] = [ IRQState::new(0x20), IRQState::new(0x21), IRQState::new(0x22), IRQState::new(0x23), IRQState::new(0x24), IRQState::new(0x25), IRQState::new(0x26), IRQState::new(0x27), IRQState::new(0x28), IRQState::new(0x29), IRQState::new(0x2A), IRQState::new(0x2B), IRQState::new(0x2C), IRQState::new(0x2D), IRQState::new(0x2E), IRQState::new(0x2F), - IRQState::new(0x30), + IRQState::new(0x30), IRQState::new(0x31), IRQState::new(0x32), IRQState::new(0x33), + IRQState::new(0x34), IRQState::new(0x35), IRQState::new(0x36), IRQState::new(0x37), ]; diff --git a/kernel/src/interrupts/irq.rs b/kernel/src/interrupts/irq.rs index eb64bef05..16e13733a 100644 --- a/kernel/src/interrupts/irq.rs +++ b/kernel/src/interrupts/irq.rs @@ -23,7 +23,7 @@ macro_rules! irq_handler { /// Array of interrupt handlers. The position in the array defines the IRQ this /// handler is targeting. See the module documentation for more information. -pub static IRQ_HANDLERS : [extern "x86-interrupt" fn(stack_frame: &mut ExceptionStackFrame); 17] = [ +pub static IRQ_HANDLERS : [extern "x86-interrupt" fn(stack_frame: &mut ExceptionStackFrame); 24] = [ irq_handler!(0, pit_handler), irq_handler!(1, keyboard_handler), irq_handler!(2, cascade_handler), @@ -41,4 +41,11 @@ pub static IRQ_HANDLERS : [extern "x86-interrupt" fn(stack_frame: &mut Exception irq_handler!(14, primary_ata_handler), irq_handler!(15, secondary_ata_handler), irq_handler!(16, hpet_handler), + irq_handler!(17, irq17_handler), + irq_handler!(18, irq18_handler), + irq_handler!(19, network_handler), + irq_handler!(20, irq20_handler), + irq_handler!(21, irq21_handler), + irq_handler!(22, irq22_handler), + irq_handler!(23, irq23_handler), ]; diff --git a/kernel/src/interrupts/syscalls.rs b/kernel/src/interrupts/syscalls.rs index aff39727a..651e9bee4 100644 --- a/kernel/src/interrupts/syscalls.rs +++ b/kernel/src/interrupts/syscalls.rs @@ -20,7 +20,7 @@ use crate::sync::RwLock; use crate::timer; use failure::Backtrace; use sunrise_libkern::{nr, SYSCALL_NAMES, MemoryInfo, MemoryAttributes, MemoryPermissions, MemoryType}; -use bit_field::BitArray; +use bit_field::{BitField, BitArray}; /// Resize the heap of a process, just like a brk. /// It can both expand, and shrink the heap. @@ -532,6 +532,14 @@ fn query_memory(mut meminfo: UserSpacePtrMut, _unk: usize, addr: usi Ok(0) } +/// Returns the current system tick (if you're a linuxian, you might call this +/// a jiffie). +/// +/// The frequency is 19200000 Hz (constant from official sw). +fn get_system_tick() -> u64 { + crate::timer::get_tick().wrapping_mul(625) / 12 // Accurate way of doing * 52.083333 +} + /// Create a new Session pair. Those sessions are linked to each-other: The /// server will receive requests sent through the client. /// @@ -687,6 +695,11 @@ pub extern fn syscall_handler_inner(registers: &mut Registers) { (true, nr::UnmapSharedMemory) => registers.apply0(unmap_shared_memory(x0 as _, x1 as _, x2 as _)), (true, nr::CloseHandle) => registers.apply0(close_handle(x0 as _)), (true, nr::WaitSynchronization) => registers.apply1(wait_synchronization(UserSpacePtr::from_raw_parts(x0 as _, x1), x2)), + (true, nr::GetSystemTick) => { + let tick = get_system_tick(); + registers.eax = tick.get_bits(0..32) as usize; + registers.ebx = tick.get_bits(32..64) as usize; + }, (true, nr::ConnectToNamedPort) => registers.apply1(connect_to_named_port(UserSpacePtr(x0 as _))), (true, nr::SendSyncRequestWithUserBuffer) => registers.apply0(send_sync_request_with_user_buffer(UserSpacePtrMut::from_raw_parts_mut(x0 as _, x1), x2 as _)), (true, nr::OutputDebugString) => registers.apply0(output_debug_string(UserSpacePtr::from_raw_parts(x0 as _, x1), x2, UserSpacePtr::from_raw_parts(x3 as _, x4))), diff --git a/kernel/src/timer.rs b/kernel/src/timer.rs index b2e076414..638dbdbec 100644 --- a/kernel/src/timer.rs +++ b/kernel/src/timer.rs @@ -19,6 +19,9 @@ struct KernelTimerInfo { /// The IRQ number that the timer use. pub irq_number: u8, + + /// Get the current tick in nanoseconds. + pub get_tick: fn() -> u64, } /// Stores the information needed for Sunrise's internal timing. @@ -29,13 +32,14 @@ static KERNEL_TIMER_INFO: Once = Once::new(); /// # Panics /// /// Panics if the timer info has already been initialized. -pub fn set_kernel_timer_info(irq_number: u8, oscillator_frequency: u64, irq_period_ns: u64) { +pub fn set_kernel_timer_info(irq_number: u8, oscillator_frequency: u64, irq_period_ns: u64, get_tick: fn() -> u64) { assert!(KERNEL_TIMER_INFO.r#try().is_none(), "Kernel Timer Info is already initialized!"); KERNEL_TIMER_INFO.call_once(|| { KernelTimerInfo { irq_number, oscillator_frequency, - irq_period_ns + irq_period_ns, + get_tick } }); } @@ -101,3 +105,13 @@ impl Waitable for IRQTimer { } } +/// Gets the current tick in nanosecond according to the [KERNEL_TIMER_INFO]. +/// +/// The tick should be monotonically increasing. Note that the underlying timer +/// might not have nanosecond precision - the HPET, for instance, has a worst +/// precision of 100ns. +pub fn get_tick() -> u64 { + let timer_info = KERNEL_TIMER_INFO.r#try().expect("Kernel Timer Info is not initialized!"); + + (timer_info.get_tick)() +} \ No newline at end of file diff --git a/libuser/Cargo.toml b/libuser/Cargo.toml index a1fee38ce..a7a656832 100644 --- a/libuser/Cargo.toml +++ b/libuser/Cargo.toml @@ -7,6 +7,7 @@ edition = "2018" [dependencies] linked_list_allocator = "0.6.4" +bitflags = "1.1.0" bitfield = "0.13" bit_field = "0.10" spin = "0.5" @@ -17,6 +18,7 @@ failure = { version = "0.1", default-features = false, features = ["derive"] } font-rs = { git = "https://github.com/SunriseOS/font-rs" } log = "0.4" lazy_static = "1.3" +getset = { git = "https://github.com/razican/getset", branch = "deref" } [dependencies.byteorder] default-features = false diff --git a/libuser/src/error.rs b/libuser/src/error.rs index c61be839a..b7abf1d08 100644 --- a/libuser/src/error.rs +++ b/libuser/src/error.rs @@ -51,6 +51,8 @@ pub enum Error { Libuser(LibuserError, Backtrace), /// Ahci driver error. Ahci(AhciError, Backtrace), + /// Virtio driver error. + Virtio(VirtioError, Backtrace), /// An unknown error type. Either someone returned a custom error, or this /// version of libuser is outdated. Unknown(u32, Backtrace) @@ -68,6 +70,7 @@ impl Error { //Module::Vi => Error::Vi(ViError(description), Backtrace::new()), Module::Libuser => Error::Libuser(LibuserError(description), Backtrace::new()), Module::Ahci => Error::Ahci(AhciError(description), Backtrace::new()), + Module::Virtio => Error::Virtio(VirtioError(description), Backtrace::new()), _ => Error::Unknown(errcode, Backtrace::new()) } } @@ -82,6 +85,7 @@ impl Error { //Error::Vi(err, ..) => err.0 << 9 | Module::Vi.0, Error::Libuser(err, ..) => err.0 << 9 | Module::Libuser.0, Error::Ahci(err, ..) => err.0 << 9 | Module::Ahci.0, + Error::Virtio(err, ..) => err.0 << 9 | Module::Virtio.0, Error::Unknown(err, ..) => err, } } @@ -110,6 +114,7 @@ enum_with_val! { Vi = 114, Libuser = 115, Ahci = 116, + Virtio = 117, } } @@ -129,6 +134,8 @@ enum_with_val! { InvalidIpcBufferCount = 5, /// Invalid IPCBuffer InvalidIpcBuffer = 6, + /// Specified BAR does not exist in this PCI device. + MissingBAR = 7, } } @@ -190,3 +197,20 @@ impl From for Error { Error::Ahci(error, Backtrace::new()) } } + +enum_with_val! { + /// Virtio errors. + #[derive(PartialEq, Eq, Clone, Copy)] + pub struct VirtioError(u32) { + /// A required PCI Vendor-Specific feature was missing. + MissingRequiredFeature = 0, + /// Failed to negociate features with the virtio device. + FeatureNegociationFailed = 1, + } +} + +impl From for Error { + fn from(error: VirtioError) -> Self { + Error::Virtio(error, Backtrace::new()) + } +} diff --git a/libuser/src/ipc/buffer.rs b/libuser/src/ipc/buffer.rs deleted file mode 100644 index 12c617124..000000000 --- a/libuser/src/ipc/buffer.rs +++ /dev/null @@ -1,336 +0,0 @@ -//! Server wrappers around IPC Buffers -//! -//! IPC Servers may accept different kinds of IPC Buffers, in order to move -//! around large amounts of data efficiently. There exists two kinds of IPC -//! Buffers, the Pointers and the Buffers. -//! -//! Pointers work in a pair of input and output: the Server pushes an InPointer -//! while the Client pushes an OutPointer. The kernel will memcpy the contents of -//! the OutPointer to the appropriate InPointer of the other side. -//! -//! Buffers work by remapping the memory from the sender to the receiver. The in -//! and out simply decide whether the memory is remapped as read-only or write- -//! only (on supported platforms. On platforms that don't have write-only memory, -//! it will be mapped RW instead). -//! -//! Those types are not meant to be used directly. The swipc-gen `dispatch` -//! function will use them automatically as an implementation detail. -//! -//! The types will auto-deref to their underlying type, allowing the user to -//! manipulate them as if they were normal pointers. - -use core::marker::PhantomData; -use crate::ipc::IPCBuffer; -use crate::error::{Error, LibuserError}; -use core::mem::{size_of, align_of}; - -// TODO: Use plain to ensure T is a valid POD type -// BODY: Plain would give us two benefits: it would do the alignment and size -// BODY: checks for us, and it would give a type-system guarantee that our T -// BODY: is valid for any arbitrary type. - -/// An incoming Pointer buffer, also known as a Type-X Buffer. -/// -/// Note that `T` should be a POD type (in other word, it should be defined for -/// all bit values). This usually means that it should be a repr(C) struct and -/// only contain numeric fields. -pub struct InPointer<'a, T: ?Sized> { - /// Address of the InBuffer in the current address space. - addr: u64, - /// Size of the InPointer. Should match the size of T, or be a multiple of - /// the size of T::Item if T is a slice. - size: u64, - /// Lifetime of the InPointer, should be bound to a [Message](crate::ipc::Message). - phantom: PhantomData<&'a T> -} - -impl<'a, T> InPointer<'a, T> { - /// Creates a new InPointer from an underlying [IPCBuffer]. - /// - /// # Panics - /// - /// Panics if the passed buffer is not a Type-X buffer. - /// - /// # Errors - /// - /// Returns an InvalidIpcBuffer error if the size does not match what was - /// expected. - /// - /// Returns an InvalidIpcBuffer error if the address is not properly aligned. - pub fn new(buf: IPCBuffer) -> Result, Error> { - assert!(buf.buftype().is_type_x()); - if buf.size != size_of::() as u64 || - buf.addr % (align_of::() as u64) != 0 { - Err(LibuserError::InvalidIpcBuffer.into()) - } else { - Ok(InPointer { - addr: buf.addr, - size: buf.size, - phantom: PhantomData - }) - } - } -} - -impl<'a, T> core::ops::Deref for InPointer<'a, T> { - type Target = T; - fn deref(&self) -> &T { - unsafe { - (self.addr as usize as *const T).as_ref().unwrap() - } - } -} - -impl<'a, T: ?Sized + core::fmt::Debug> core::fmt::Debug for InPointer<'a, T> { - fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { - f.debug_tuple("InPointer") - .field(&*self) - .finish() - } -} - -impl<'a, T> InPointer<'a, [T]> { - /// Creates a new InPointer from an underlying [IPCBuffer]. - /// - /// # Panics - /// - /// Panics if the passed buffer is not a Type-X buffer. - /// - /// # Errors - /// - /// Returns a InvalidIpcBuffer error if the size does not match what was - /// expected - pub fn new(buf: IPCBuffer) -> Result, Error> { - assert!(buf.buftype().is_type_x()); - if buf.size % size_of::() as u64 != 0 || buf.size == 0 || - buf.addr % (align_of::() as u64) != 0 { - Err(LibuserError::InvalidIpcBuffer.into()) - } else { - Ok(InPointer { - addr: buf.addr, - size: buf.size, - phantom: PhantomData - }) - } - } -} - -impl<'a, T> core::ops::Deref for InPointer<'a, [T]> { - type Target = [T]; - fn deref(&self) -> &[T] { - unsafe { - core::slice::from_raw_parts(self.addr as usize as *const T, - self.size as usize / size_of::()) - } - } -} - -/// An incoming Buffer, also known as a Type-A Buffer. -/// -/// Note that `T` should be a POD type (in other word, it should be defined for -/// all bit values). This usually means that it should be a repr(C) struct and -/// only contain numeric fields. -pub struct InBuffer<'a, T: ?Sized> { - /// Address of the InBuffer in the current address space. - addr: u64, - /// Size of the InBuffer. Should match the size of T, or be a multiple of - /// the size of T::Item if T is a slice. - size: u64, - /// Lifetime of the InBuffer, should be bound to a [Message](crate::ipc::Message). - phantom: PhantomData<&'a T> -} - - -impl<'a, T> InBuffer<'a, T> { - /// Creates a new InBuffer from an underlying [IPCBuffer]. - /// - /// # Panics - /// - /// Panics if the passed buffer is not a Type-A buffer. - /// - /// # Errors - /// - /// Returns a InvalidIpcBuffer error if the size does not match what was - /// expected - pub fn new(buf: IPCBuffer) -> Result, Error> { - assert!(buf.buftype().is_type_a()); - if buf.size != size_of::() as u64 || - buf.addr % (align_of::() as u64) != 0 { - Err(LibuserError::InvalidIpcBuffer.into()) - } else { - Ok(InBuffer { - addr: buf.addr, - size: buf.size, - phantom: PhantomData - }) - } - } -} - -impl<'a, T> core::ops::Deref for InBuffer<'a, T> { - type Target = T; - fn deref(&self) -> &T { - unsafe { - (self.addr as usize as *const T).as_ref().unwrap() - } - } -} - -impl<'a, T: ?Sized + core::fmt::Debug> core::fmt::Debug for InBuffer<'a, T> { - fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { - f.debug_tuple("InBuffer") - .field(&*self) - .finish() - } -} - - -impl<'a, T> InBuffer<'a, [T]> { - /// Creates a new InBuffer from an underlying [IPCBuffer]. - /// - /// # Panics - /// - /// Panics if the passed buffer is not a Type-A buffer. - /// - /// # Errors - /// - /// Returns a InvalidIpcBuffer error if the size does not match what was - /// expected - pub fn new(buf: IPCBuffer) -> Result, Error> { - assert!(buf.buftype().is_type_a()); - if buf.size % size_of::() as u64 != 0 || buf.size == 0 || - buf.addr % (align_of::() as u64) != 0 { - Err(LibuserError::InvalidIpcBuffer.into()) - } else { - Ok(InBuffer { - addr: buf.addr, - size: buf.size, - phantom: PhantomData - }) - } - } -} - -impl<'a, T> core::ops::Deref for InBuffer<'a, [T]> { - type Target = [T]; - fn deref(&self) -> &[T] { - unsafe { - core::slice::from_raw_parts(self.addr as usize as *const T, - self.size as usize / size_of::()) - } - } -} - - -/// An outcoming Buffer, also known as a Type-B Buffer. -/// -/// Note that `T` should be a POD type (in other word, it should be defined for -/// all bit values). This usually means that it should be a repr(C) struct and -/// only contain numeric fields. -pub struct OutBuffer<'a, T: ?Sized> { - /// Address of the OutBuffer in the current address space. - addr: u64, - /// Size of the OutBuffer. Should match the size of T, or be a multiple of - /// the size of T::Item if T is a slice. - size: u64, - /// Lifetime of the OutBuffer, should be bound to a [Message](crate::ipc::Message). - phantom: PhantomData<&'a T> -} - - -impl<'a, T> OutBuffer<'a, T> { - /// Creates a new OutBuffer from an underlying [IPCBuffer]. - /// - /// # Panics - /// - /// Panics if the passed buffer is not a Type-A buffer. - /// - /// # Errors - /// - /// Returns a InvalidIpcBuffer error if the size does not match what was - /// expected - pub fn new(buf: IPCBuffer) -> Result, Error> { - assert!(buf.buftype().is_type_b()); - if buf.size != size_of::() as u64 || - buf.addr % (align_of::() as u64) != 0 { - Err(LibuserError::InvalidIpcBuffer.into()) - } else { - Ok(OutBuffer { - addr: buf.addr, - size: buf.size, - phantom: PhantomData - }) - } - } -} - -impl<'a, T> core::ops::Deref for OutBuffer<'a, T> { - type Target = T; - fn deref(&self) -> &T { - unsafe { - (self.addr as usize as *const T).as_ref().unwrap() - } - } -} - -impl<'a, T> core::ops::DerefMut for OutBuffer<'a, T> { - fn deref_mut(&mut self) -> &mut T { - unsafe { - (self.addr as usize as *mut T).as_mut().unwrap() - } - } -} - -impl<'a, T: ?Sized + core::fmt::Debug> core::fmt::Debug for OutBuffer<'a, T> { - fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { - f.debug_tuple("OutBuffer") - .field(&*self) - .finish() - } -} - - -impl<'a, T> OutBuffer<'a, [T]> { - /// Creates a new OutBuffer from an underlying [IPCBuffer]. - /// - /// # Panics - /// - /// Panics if the passed buffer is not a Type-B buffer. - /// - /// # Errors - /// - /// Returns a InvalidIpcBuffer error if the size does not match what was - /// expected - pub fn new(buf: IPCBuffer) -> Result, Error> { - assert!(buf.buftype().is_type_b()); - if buf.size % size_of::() as u64 != 0 || buf.size == 0 || - buf.addr % (align_of::() as u64) != 0 { - Err(LibuserError::InvalidIpcBuffer.into()) - } else { - Ok(OutBuffer { - addr: buf.addr, - size: buf.size, - phantom: PhantomData - }) - } - } -} - -impl<'a, T> core::ops::Deref for OutBuffer<'a, [T]> { - type Target = [T]; - fn deref(&self) -> &[T] { - unsafe { - core::slice::from_raw_parts(self.addr as usize as *const T, - self.size as usize / size_of::()) - } - } -} - -impl<'a, T> core::ops::DerefMut for OutBuffer<'a, [T]> { - fn deref_mut(&mut self) -> &mut [T] { - unsafe { - core::slice::from_raw_parts_mut(self.addr as usize as *mut T, - self.size as usize / size_of::()) - } - } -} diff --git a/libuser/src/ipc/mod.rs b/libuser/src/ipc/mod.rs index 33fcecb53..2b4606992 100644 --- a/libuser/src/ipc/mod.rs +++ b/libuser/src/ipc/mod.rs @@ -16,6 +16,7 @@ use core::marker::PhantomData; use core::mem; +use core::convert::TryFrom; use byteorder::{ByteOrder, LE}; use arrayvec::{ArrayVec, Array}; use crate::utils::{self, align_up, CursorWrite, CursorRead}; @@ -24,8 +25,6 @@ use bit_field::BitField; use crate::error::{Error, LibuserError}; pub mod server; -mod buffer; -pub use buffer::*; bitfield! { /// Represenens the header of an HIPC command. @@ -179,27 +178,62 @@ pub struct IPCBuffer<'a> { pub trait SizedIPCBuffer { /// Return the size of the type. fn size(&self) -> usize; + + /// Check if the address and size are correct. + fn is_cool(addr: usize, size: usize) -> bool; + + /// Create a reference to a ipc buffer from part. + unsafe fn from_raw_parts<'a>(addr: usize, size: usize) -> &'a Self; + + /// Create a mutable reference to a ipc buffer from part. + unsafe fn from_raw_parts_mut<'a>(addr: usize, size: usize) -> &'a mut Self; } impl SizedIPCBuffer for T { fn size(&self) -> usize { core::mem::size_of::() } + + fn is_cool(addr: usize, size: usize) -> bool { + size == core::mem::size_of::() && + (addr % core::mem::align_of::()) == 0 && addr != 0 + } + + unsafe fn from_raw_parts<'a>(addr: usize, _size: usize) -> &'a Self { + (addr as *const T).as_ref().unwrap() + } + + unsafe fn from_raw_parts_mut<'a>(addr: usize, _size: usize) -> &'a mut Self { + (addr as *mut T).as_mut().unwrap() + } } impl SizedIPCBuffer for [T] { fn size(&self) -> usize { core::mem::size_of::() * self.len() } + + fn is_cool(addr: usize, size: usize) -> bool { + size % core::mem::size_of::() == 0 && size != 0 && + (addr % core::mem::align_of::()) == 0 && addr != 0 + } + + unsafe fn from_raw_parts<'a>(addr: usize, size: usize) -> &'a Self { + core::slice::from_raw_parts(addr as *const T, size / core::mem::size_of::()) + } + + unsafe fn from_raw_parts_mut<'a>(addr: usize, size: usize) -> &'a mut Self { + core::slice::from_raw_parts_mut(addr as *mut T, size / core::mem::size_of::()) + } } impl<'a> IPCBuffer<'a> { /// Creates a Type-A IPCBuffer from the given reference. - fn out_buffer(data: &T, flags: u8) -> IPCBuffer { + fn out_buffer(data: Option<&T>, flags: u8) -> IPCBuffer { IPCBuffer { - addr: data as *const T as *const u8 as usize as u64, + addr: data.map(|v| v as *const T as *const u8 as usize as u64).unwrap_or(0), // The dereference is necessary because &T implements SizedIPCBuffer too... - size: (*data).size() as u64, + size: data.map(|v| (*v).size() as u64).unwrap_or(0), ty: IPCBufferType::A { flags }, @@ -208,11 +242,11 @@ impl<'a> IPCBuffer<'a> { } /// Creates a Type-B IPCBuffer from the given reference. - fn in_buffer(data: &mut T, flags: u8) -> IPCBuffer { + fn in_buffer(mut data: Option<&mut T>, flags: u8) -> IPCBuffer { IPCBuffer { - addr: data as *mut T as *const u8 as usize as u64, + addr: data.as_mut().map(|v| *v as *mut T as *const u8 as usize as u64).unwrap_or(0), // The dereference is necessary because &T implements SizedIPCBuffer too... - size: (*data).size() as u64, + size: data.as_mut().map(|v| (**v).size() as u64).unwrap_or(0), ty: IPCBufferType::B { flags }, @@ -224,11 +258,11 @@ impl<'a> IPCBuffer<'a> { /// /// If has_u16_size is true, the size of the pointer will be written after /// the raw data. This is only used when sending client-sized arrays. - fn in_pointer(data: &mut T, has_u16_size: bool) -> IPCBuffer { + fn in_pointer(mut data: Option<&mut T>, has_u16_size: bool) -> IPCBuffer { IPCBuffer { - addr: data as *mut T as *const u8 as usize as u64, + addr: data.as_mut().map(|v| *v as *mut T as *const u8 as usize as u64).unwrap_or(0), // The dereference is necessary because &T implements SizedIPCBuffer too... - size: (*data).size() as u64, + size: data.as_mut().map(|v| (**v).size() as u64).unwrap_or(0), ty: IPCBufferType::C { has_u16_size }, @@ -239,11 +273,11 @@ impl<'a> IPCBuffer<'a> { /// Creates a Type-X IPCBuffer from the given reference. /// /// The counter defines which type-C buffer this should be copied into. - fn out_pointer(data: &T, counter: u8) -> IPCBuffer { + fn out_pointer(data: Option<&T>, counter: u8) -> IPCBuffer { IPCBuffer { - addr: data as *const T as *const u8 as usize as u64, + addr: data.map(|v| v as *const T as *const u8 as usize as u64).unwrap_or(0), // The dereference is necessary because &T implements SizedIPCBuffer too... - size: (*data).size() as u64, + size: data.map(|v| (*v).size() as u64).unwrap_or(0), ty: IPCBufferType::X { counter }, @@ -527,11 +561,19 @@ where /// /// This method is unsafe as it allows creating references to arbitrary /// memory, and of arbitrary type. - pub unsafe fn pop_in_buffer(&mut self) -> Result, Error> { - self.buffers.iter().position(|buf| buf.buftype().is_type_a()) + pub unsafe fn pop_in_buffer<'b, T: SizedIPCBuffer + ?Sized>(&mut self) -> Result<&'b T, Error> { + let buffer = self.buffers.iter().position(|buf| buf.buftype().is_type_a()) .and_then(|pos| self.buffers.pop_at(pos)) - .ok_or_else(|| LibuserError::InvalidIpcBufferCount.into()) - .and_then(|buf| InBuffer::::new(buf)) + .ok_or_else(|| LibuserError::InvalidIpcBufferCount)?; + + let addr = usize::try_from(buffer.addr).map_err(|_| LibuserError::InvalidIpcBuffer)?; + let size = usize::try_from(buffer.size).map_err(|_| LibuserError::InvalidIpcBuffer)?; + + if T::is_cool(addr, size) { + Ok(T::from_raw_parts(addr, size)) + } else { + Err(LibuserError::InvalidIpcBuffer.into()) + } } /// Retreive the next OutBuffer (type-B buffer) in the message. @@ -547,22 +589,30 @@ where /// /// This method is unsafe as it allows creating references to arbitrary /// memory, and of arbitrary type. - pub unsafe fn pop_out_buffer(&mut self) -> Result, Error> { - self.buffers.iter().position(|buf| buf.buftype().is_type_b()) + pub unsafe fn pop_out_buffer<'b, T: SizedIPCBuffer + ?Sized>(&mut self) -> Result<&'b mut T, Error> { + let buffer = self.buffers.iter().position(|buf| buf.buftype().is_type_b()) .and_then(|pos| self.buffers.pop_at(pos)) - .ok_or_else(|| LibuserError::InvalidIpcBufferCount.into()) - .and_then(|buf| OutBuffer::::new(buf)) + .ok_or_else(|| LibuserError::InvalidIpcBufferCount)?; + + let addr = usize::try_from(buffer.addr).map_err(|_| LibuserError::InvalidIpcBuffer)?; + let size = usize::try_from(buffer.size).map_err(|_| LibuserError::InvalidIpcBuffer)?; + + if T::is_cool(addr, size) { + Ok(T::from_raw_parts_mut(addr, size)) + } else { + Err(LibuserError::InvalidIpcBuffer.into()) + } } /// Push an OutBuffer (type-A buffer) backed by the specified data. pub fn push_out_buffer(&mut self, data: &'a T) -> &mut Self { - self.buffers.push(IPCBuffer::out_buffer(data, 0)); + self.buffers.push(IPCBuffer::out_buffer(Some(data), 0)); self } /// Push an InBuffer (type-B buffer) backed by the specified data. pub fn push_in_buffer(&mut self, data: &'a mut T) -> &mut Self { - self.buffers.push(IPCBuffer::in_buffer(data, 0)); + self.buffers.push(IPCBuffer::in_buffer(Some(data), 0)); self } @@ -572,7 +622,7 @@ where /// to the Raw Data. See Buffer 0xA on the IPC Marshalling page of /// switchbrew. pub fn push_in_pointer(&mut self, data: &'a mut T, has_u16_count: bool) -> &mut Self { - self.buffers.push(IPCBuffer::in_pointer(data, has_u16_count)); + self.buffers.push(IPCBuffer::in_pointer(Some(data), has_u16_count)); self } @@ -583,10 +633,35 @@ where /// switchbrew. pub fn push_out_pointer(&mut self, data: &'a T) -> &mut Self { let index = self.buffers.iter().filter(|buf| buf.buftype().is_type_x()).count(); - self.buffers.push(IPCBuffer::out_pointer(data, index as u8)); + self.buffers.push(IPCBuffer::out_pointer(Some(data), index as u8)); + self + } + + pub fn push_in_smart_pointer(&mut self, pointer_buffer_size: u16, data: &'a mut T) -> &mut Self { + if pointer_buffer_size != 0 && (*data).size() <= usize::from(pointer_buffer_size) { + self.buffers.push(IPCBuffer::in_pointer(Some(data), false)); + self.buffers.push(IPCBuffer::in_buffer::(None, 0)); + } else { + self.buffers.push(IPCBuffer::in_pointer::(None, false)); + self.buffers.push(IPCBuffer::in_buffer(Some(data), 0)); + } + self + } + + pub fn push_out_smart_pointer(&mut self, pointer_buffer_size: u16, data: &'a T) -> &mut Self { + let index = self.buffers.iter().filter(|buf| buf.buftype().is_type_x()).count(); + if pointer_buffer_size != 0 && (*data).size() <= usize::from(pointer_buffer_size) { + self.buffers.push(IPCBuffer::out_pointer(Some(data), index as u8)); + self.buffers.push(IPCBuffer::out_buffer::(None, 0)); + } else { + self.buffers.push(IPCBuffer::out_pointer::(None, index as u8)); + self.buffers.push(IPCBuffer::out_buffer(Some(data), 0)); + } self } + + /// Send a Pid with this IPC request. /// /// If `pid` is None, sends the current process' Pid. If it's Some, then it @@ -610,11 +685,69 @@ where /// /// This method is unsafe as it allows creating references to arbitrary /// memory, and of arbitrary type. - pub unsafe fn pop_in_pointer(&mut self) -> Result, Error> { - self.buffers.iter().position(|buf| buf.buftype().is_type_x()) + pub unsafe fn pop_in_pointer<'b, T: SizedIPCBuffer + ?Sized>(&mut self) -> Result<&'b T, Error> { + let buffer = self.buffers.iter().position(|buf| buf.buftype().is_type_x()) + .and_then(|pos| self.buffers.pop_at(pos)) + .ok_or_else(|| LibuserError::InvalidIpcBufferCount)?; + + let addr = usize::try_from(buffer.addr).map_err(|_| LibuserError::InvalidIpcBuffer)?; + let size = usize::try_from(buffer.size).map_err(|_| LibuserError::InvalidIpcBuffer)?; + + if T::is_cool(addr, size) { + Ok(T::from_raw_parts(addr, size)) + } else { + Err(LibuserError::InvalidIpcBuffer.into()) + } + } + + pub unsafe fn pop_in_smart_buffer<'b, T: SizedIPCBuffer + ?Sized>(&mut self) -> Result<&'b T, Error> { + let pointer = self.buffers.iter().position(|buf| buf.buftype().is_type_x()) + .and_then(|pos| self.buffers.pop_at(pos)) + .ok_or_else(|| LibuserError::InvalidIpcBufferCount)?; + + let buffer = self.buffers.iter().position(|buf| buf.buftype().is_type_a()) + .and_then(|pos| self.buffers.pop_at(pos)) + .ok_or_else(|| LibuserError::InvalidIpcBufferCount)?; + + let (addr, size) = if pointer.addr != 0 { + (pointer.addr, pointer.size) + } else { + (buffer.addr, buffer.size) + }; + + let addr = usize::try_from(addr).map_err(|_| LibuserError::InvalidIpcBuffer)?; + let size = usize::try_from(size).map_err(|_| LibuserError::InvalidIpcBuffer)?; + + if T::is_cool(addr, size) { + Ok(T::from_raw_parts(addr, size)) + } else { + Err(LibuserError::InvalidIpcBuffer.into()) + } + } + + pub unsafe fn pop_out_smart_buffer<'b, T: SizedIPCBuffer + ?Sized>(&mut self) -> Result<&'b mut T, Error> { + let pointer = self.buffers.iter().position(|buf| buf.buftype().is_type_x()) + .and_then(|pos| self.buffers.pop_at(pos)) + .ok_or_else(|| LibuserError::InvalidIpcBufferCount)?; + + let buffer = self.buffers.iter().position(|buf| buf.buftype().is_type_a()) .and_then(|pos| self.buffers.pop_at(pos)) - .ok_or_else(|| LibuserError::InvalidIpcBufferCount.into()) - .and_then(|buf| InPointer::::new(buf)) + .ok_or_else(|| LibuserError::InvalidIpcBufferCount)?; + + let (addr, size) = if pointer.addr != 0 { + (pointer.addr, pointer.size) + } else { + (buffer.addr, buffer.size) + }; + + let addr = usize::try_from(addr).map_err(|_| LibuserError::InvalidIpcBuffer)?; + let size = usize::try_from(size).map_err(|_| LibuserError::InvalidIpcBuffer)?; + + if T::is_cool(addr, size) { + Ok(T::from_raw_parts_mut(addr, size)) + } else { + Err(LibuserError::InvalidIpcBuffer.into()) + } } // TODO: Move pack to a non-generic function diff --git a/libuser/src/ipc/server.rs b/libuser/src/ipc/server.rs index 80dc2b597..8d080ed07 100644 --- a/libuser/src/ipc/server.rs +++ b/libuser/src/ipc/server.rs @@ -292,6 +292,31 @@ where Ok(false) }, Some((2, _)) => Ok(true), + Some((5, 0)) | Some((7, 0)) => { + // ConvertCurrentObjectToDomain, unsupported + Ok(true) + }, + Some((5, 1)) | Some((7, 1)) => { + // CopyFromCurrentDomain, unsupported + Ok(true) + }, + Some((5, 2)) | Some((7, 2)) => { + // CloneCurrentObject, unsupported + Ok(true) + }, + Some((5, 3)) | Some((7, 3)) => { + // QueryPointerBufferSize + let mut msg__ = Message::::new_response(None); + msg__.push_raw(self.pointer_buf.len() as u16); + msg__.pack(&mut self.buf[..]); + self.handle.reply(&mut self.buf[..])?; + Ok(false) + }, + Some((5, 4)) | Some((7, 4)) => { + // CloneCurrentObjectEx, unsupported + Ok(true) + }, + _ => Ok(true) } } diff --git a/libuser/src/lib.rs b/libuser/src/lib.rs index d55076115..a7e7ac51a 100644 --- a/libuser/src/lib.rs +++ b/libuser/src/lib.rs @@ -56,6 +56,7 @@ pub mod allocator; pub mod terminal; pub mod window; pub mod zero_box; +pub mod pci; mod crt0; mod log_impl; diff --git a/libuser/src/mem.rs b/libuser/src/mem.rs index b9827fe92..cfc700af3 100644 --- a/libuser/src/mem.rs +++ b/libuser/src/mem.rs @@ -64,6 +64,17 @@ pub fn map_mmio(physical_address: usize) -> Result<*mut T, KernelError> { Ok((virt_addr + (physical_address % PAGE_SIZE)) as *mut T) } +/// Maps a range of bytes. +/// +/// This function preserves the offset relative to `PAGE_SIZE`. +pub fn map_mmio_range(physical_address: usize, len: usize) -> Result<*mut u8, KernelError> { + let aligned_phys_addr = align_down(physical_address, PAGE_SIZE); + let full_size = align_up(aligned_phys_addr + len, PAGE_SIZE) - aligned_phys_addr; + let virt_addr = find_free_address(full_size as _, 1).unwrap(); + syscalls::map_mmio_region(aligned_phys_addr as _, full_size as _, virt_addr, true)?; + Ok((virt_addr + (physical_address % PAGE_SIZE)) as *mut u8) +} + /// Gets the physical address of a structure from its virtual address, preserving offset in the page. /// /// # Panics diff --git a/libuser/src/pci.rs b/libuser/src/pci.rs new file mode 100644 index 000000000..038dd89bb --- /dev/null +++ b/libuser/src/pci.rs @@ -0,0 +1,772 @@ +//! PCI discovery +//! +//! A minimal PCI implementation, that permits only discovering AHCI devices, and querying their BAR. +//! +//! PCI Local Bus Specification: https://web.archive.org/web/20170728023923/https://lekensteyn.nl/files/docs/PCI_SPEV_V3_0.pdf +//! +//! (Careful, there are many outdated PCI specs documents out there that are +//! incompatible with the real spec, especially in regards to MSI-X!) + +use sunrise_libutils::io::{Io, Pio}; +use spin::Mutex; +use getset::Getters; +use bit_field::BitField; +use alloc::vec::Vec; +use crate::types::MappedSharedMemory; +use crate::error::{LibuserError, KernelError, Error}; +use byteorder::ByteOrder; +use capabilities::Capability; +use bitflags::bitflags; + +pub mod capabilities; + +/// The CONFIG_ADDRESS I/O location. +pub const CONFIG_ADDRESS: u16 = 0xCF8; +/// The CONFIG_DATA I/O location. +pub const CONFIG_DATA: u16 = 0xCFC; + +/// Offset of first capability list entry. +const PCI_CAPABILITY_LIST: usize = 0x34; + +/// A struct tying the two pci config ports together. +#[derive(Debug)] +struct PciConfigPortsPair { + /// The address port. + /// + /// Write the '''address''' of the config-space register you want to access. + /// + /// An address is formatted as follow: + /// + /// * 31 Enable bit + /// * 30:24 Reserved + /// * 23:16 Bus Number + /// * 15:11 Device Number + /// * 10:8 Function Number + /// * 7:0 Register Offset + address: Pio, + /// The data port. + /// + /// After having put the address of the register you want in `.address`, + /// read this port to retrieve its value. + data: Pio +} + +/// A mutex around the two ports used to address pci configuration space. +static PCI_CONFIG_PORTS: Mutex = Mutex::new(PciConfigPortsPair { + address: Pio::new(CONFIG_ADDRESS), + data: Pio::new(CONFIG_DATA) +}); + +/// The highest addressable bus. +const MAX_BUS: u8 = 255; +/// The highest addressable slot on a bus. +const MAX_SLOT: u8 = 31; +/// The highest addressable function on a slot on a bus. +const MAX_FUNC: u8 = 15; +/// The highest addressable register on a function on a slot on a bus. +const MAX_REGISTER: u8 = 63 * 4; + +/// A pci device, addressed by its bus number, slot, and function. +#[derive(Debug, Copy, Clone, Getters)] +#[allow(clippy::missing_docs_in_private_items)] +pub struct PciDevice { + /// The device's bus number. + #[get = "pub"] #[deref] + bus: u8, + /// The device's slot number on its bus. + #[get = "pub"] #[deref] + slot: u8, + /// The device's function number. + #[get = "pub"] #[deref] + function: u8, + + /* [register 0x00] */ + /// Identifies the particular device. Where valid IDs are allocated by the vendor. + #[get = "pub"] #[deref] + did: u16, + /// Identifies the manufacturer of the device. Where valid IDs are allocated by PCI-SIG (the list + /// is [here]) to ensure uniqueness and 0xFFFF is an invalid value that will be returned on read + /// accesses to Configuration Space registers of non-existent devices. + /// + /// [here]: https://pcisig.com/membership/member-companies + #[get = "pub"] #[deref] + vid: u16, + /* [register 0x01] */ + /* status + command are volatile */ + /* [register 0x02] */ + /// Specifies the type of function the device performs. + #[get = "pub"] #[deref] + class: u8, + /// Specifies the specific function the device performs. + #[get = "pub"] #[deref] + subclass: u8, + /// Specifies a register-level programming interface the device has, if it has any at all. + #[get = "pub"] #[deref] + prog_if: u8, + /// Specifies a revision identifier for a particular device. Where valid IDs are allocated by the vendor. + #[get = "pub"] #[deref] + rev_id: u8, + /* [register 0x03] */ + /* bist is volatile */ + header_type: u8, + /// Specifies the latency timer in units of PCI bus clocks. + #[get = "pub"] #[deref] + latency_timer: u8, + /// Specifies the system cache line size in 32-bit units. A device can limit the number of + /// cacheline sizes it can support, if a unsupported value is written to this field, the device + /// will behave as if a value of 0 was written. + #[get = "pub"] #[deref] + cache_line_size: u8, + + /// Remaining registers values, based on header type. + #[get = "pub"] #[deref] + header: PciHeader +} + +/// Pci header when Header Type == 0x00 (General device). +#[derive(Copy, Clone, Debug, Getters)] +#[allow(clippy::missing_docs_in_private_items)] +pub struct GeneralPciHeader { + bars: [Option; 6], + /// Points to the Card Information Structure and is used by devices that share silicon between + /// CardBus and PCI. + #[get] #[deref] + cardbus_cis_ptr: u32, + /// Subsystem ID + #[get = "pub"] #[deref] + subsystem_id: u16, + subsystem_vendor_id: u16, + expansion_rom_base_address: u32, + /// Points to a linked list of new capabilities implemented by the device. Used if bit 4 of the + /// status register (Capabilities List bit) is set to 1. The bottom two bits are reserved and + /// should be masked before the Pointer is used to access the Configuration Space. + #[get] #[deref] + capabilities_ptr: u8, + /// Specifies how often the device needs access to the PCI bus (in 1/4 microsecond units). + #[get] #[deref] + max_latency: u8, + /// Specifies the burst period length, in 1/4 microsecond units, that the device needs (assuming + /// a 33 MHz clock rate). + #[get] #[deref] + min_grant: u8, + /// Specifies which interrupt pin the device uses. Where a value of 0x01 is INTA#, 0x02 is + /// INTB#, 0x03 is INTC#, 0x04 is INTD#, and 0x00 means the device does not use an interrupt + /// pin. + #[get] #[deref] + interrupt_pin: u8, + /// Specifies which input of the system interrupt controllers the device's interrupt pin is + /// connected to and is implemented by any device that makes use of an interrupt pin. For the + /// x86 architecture this register corresponds to the PIC IRQ numbers 0-15 (and not I/O APIC IRQ + /// numbers) and a value of 0xFF defines no connection. + #[get = "pub"] #[deref] + interrupt_line: u8, +} + +impl GeneralPciHeader { + /// Get the Base Address Register at the specified index. + /// + /// # Errors + /// + /// * [LibuserError::MissingBAR] + /// * `idx` is bigger than 5 + /// * Specified BAR does not exist (happens if the previous BAR was 64-bit). + pub fn bar(&self, idx: usize) -> Result<&BAR, Error> { + self.bars.get(idx).and_then(|x| x.as_ref()).ok_or(LibuserError::MissingBAR.into()) + } + + /// Get the 6 Base Address Registers associated with this device. + pub fn bars(&self) -> &[Option; 6] { + &self.bars + } +} + +/// Contents of pci config registers 0x4-0xf, structure varies based on Header Type. +#[derive(Copy, Clone, Debug)] +pub enum PciHeader { + /// header type == 0x00 + GeneralDevice(GeneralPciHeader), + /// header type == 0x01, not implemented + PCItoPCIBridge, + /// header type == 0x02, not implemented + CardBus, + /// header type == other + UnknownHeaderType(u8) +} + +#[derive(Debug, Clone, Copy, Getters)] +pub struct BARMemory { + #[get = "pub"] #[deref] + phys_addr: u32, + #[get = "pub"] #[deref] + size: u32, +} + +#[derive(Debug, Clone, Copy, Getters)] +pub struct BARMemory64 { + #[get = "pub"] #[deref] + phys_addr: u64, + #[get = "pub"] #[deref] + size: u64, +} + +#[derive(Debug, Clone, Copy, Getters)] +pub struct BARIo { + #[get = "pub"] #[deref] + bus: u8, + #[get = "pub"] #[deref] + slot: u8, + #[get = "pub"] #[deref] + func: u8, + #[get = "pub"] #[deref] + register: u32, + #[get = "pub"] #[deref] + size: u32 +} + +/// Base Address Registers. Minimal implementation, does not support 64-bits BARs. +#[derive(Copy, Clone, Debug)] +pub enum BAR { + /// a memory space address and its size + Memory(BARMemory), + /// a 64-bit memory space address and its size + Memory64(BARMemory64), + /// an IO space address + Io(BARIo) +} + +bitflags! { + /// Command Register Layout + /// + /// See Chapter 6.2.2: Device Control + pub struct Command: u16 { + /// Controls a device's response to I/O Space accesses. A value of 0 + /// disables the device response. A value of 1 allows the device to + /// respond to I/O Space accesses. State after RST# is 0. + const IO_SPACE = 1 << 0; + /// Controls a device's response to Memory Space accesses. A value of 0 + /// disables the device response. A value of 1 allows the device to + /// respond to Memory Space accesses. State after RST# is 0. + const MEMORY_SPACE = 1 << 1; + /// Controls a device's ability to act as a master on the PCI bus. A + /// value of 0 disables the device from generating PCI accesses. A value + /// of 1 allows the device to behave as a bus master. State after RST# + /// is 0. + /// + /// Bus Mastering is basically DMA for PCI. + const BUS_MASTER = 1 << 2; + /// Controls a device's action on Special Cycle operations. A value of 0 + /// causes the device to ignore all Special Cycle operations. A value of + /// 1 allows the device to monitor Special Cycle operations. State after + /// RST# is 0. + const SPECIAL_CYCLES = 1 << 3; + const MEMORY_WRITE_AND_INVALIDATE_ENABLE = 1 << 4; + const VGA_PALETTE_SNOOP = 1 << 5; + const PARITY_ERROR_RESPONSE = 1 << 6; + const SERR_ENABLE = 1 << 8; + const FAST_BACK_TO_BACK_ENABLE = 1 << 9; + const INTERRUPT_DISABLE = 1 << 10; + } +} + +impl BAR { + pub fn map(&self) -> Result { + match self { + BAR::Memory(memory) => { + Ok(MappedBAR::Memory(MappedBARMemory { + phys_addr: memory.phys_addr, + virt_addr: crate::mem::map_mmio_range(memory.phys_addr as usize, memory.size as usize)?, + size: memory.size, + })) + }, + BAR::Memory64(memory) => { + Ok(MappedBAR::Memory64(MappedBARMemory64 { + phys_addr: memory.phys_addr, + virt_addr: crate::mem::map_mmio_range(memory.phys_addr as usize, memory.size as usize)?, + size: memory.size, + })) + }, + BAR::Io(io) => Ok(MappedBAR::Io(io.clone())) + } + } + +} + +#[derive(Debug)] +pub struct MappedBARMemory { + phys_addr: u32, + /// Virtual address of the BAR. + virt_addr: *mut u8, + size: u32, +} + +impl Drop for MappedBARMemory { + fn drop(&mut self) { + // TODO: Unmap the virt_addr + } +} + +#[derive(Debug)] +pub struct MappedBARMemory64 { + phys_addr: u64, + virt_addr: *mut u8, + size: u64, +} + +impl Drop for MappedBARMemory64 { + fn drop(&mut self) { + // TODO: Unmap the virt_addr + } +} + +#[derive(Debug)] +pub enum MappedBAR { + Memory(MappedBARMemory), + Memory64(MappedBARMemory64), + Io(BARIo) +} + +impl MappedBAR { + fn size(&self) -> u64 { + match self { + MappedBAR::Io(BARIo { size, .. }) => *size as u64, + MappedBAR::Memory(MappedBARMemory { size, .. }) => *size as u64, + MappedBAR::Memory64(MappedBARMemory64 { size, .. }) => *size, + } + } + + pub fn read_u8(&self, offset: u64) -> u8 { + // First, check the offset is within the size + assert!(offset < self.size(), "Out of bound read: {} < {}", offset, self.size()); + + let addr = match self { + MappedBAR::Io(bar) => { + // Handle IO-Ports + let offset = bar.register as u64 + offset; + let val = pci_config_read_word(bar.bus, bar.slot, bar.func, offset as u8 & 0xFC); + return val.to_ne_bytes()[offset.get_bits(0..2) as usize] + }, + MappedBAR::Memory(MappedBARMemory { virt_addr, .. }) => *virt_addr, + MappedBAR::Memory64(MappedBARMemory64 { virt_addr, .. }) => *virt_addr, + }; + + // Handle Memory. + unsafe { + addr.wrapping_add(offset as usize).read_volatile() + } + } + + pub fn read_u16(&self, offset: u64) -> u16 { + // First, check the offset is within the size + assert!(offset.saturating_add(1) < self.size(), "Out of bound read: {} + 1 < {}", offset, self.size()); + + let addr = match self { + MappedBAR::Io(bar) => { + // Handle IO-Ports + let offset = bar.register as u64 + offset; + let val = pci_config_read_word(bar.bus, bar.slot, bar.func, offset as u8 & 0xFE); + return BO::read_u16(&val.to_ne_bytes()[offset.get_bits(0..2) as usize..]); + }, + MappedBAR::Memory(MappedBARMemory { virt_addr, .. }) => *virt_addr, + MappedBAR::Memory64(MappedBARMemory64 { virt_addr, .. }) => *virt_addr, + }; + + // Handle Memory. + unsafe { + (addr.wrapping_add(offset as usize) as *mut u16).read_volatile() + } + } + + pub fn read_u32(&self, offset: u64) -> u32 { + // First, check the offset is within the size + assert!(offset.saturating_add(3) < self.size(), "Out of bound read: {} + 3 < {}", offset, self.size()); + + let addr = match self { + MappedBAR::Io(bar) => { + // Handle IO-Ports + let offset = bar.register as u64 + offset; + let val = pci_config_read_word(bar.bus, bar.slot, bar.func, offset as u8 & 0xFE); + return BO::read_u32(&val.to_ne_bytes()); + }, + MappedBAR::Memory(MappedBARMemory { virt_addr, .. }) => *virt_addr, + MappedBAR::Memory64(MappedBARMemory64 { virt_addr, .. }) => *virt_addr, + }; + + // Handle Memory. + unsafe { + (addr.wrapping_add(offset as usize) as *mut u32).read_volatile() + } + } + + pub fn write_u8(&self, offset: u64, data: u8) { + // First, check the offset is within the size + assert!(offset < self.size(), "Out of bound write: {} < {}", offset, self.size()); + + let addr = match self { + MappedBAR::Io(bar) => { + // Handle IO-Ports + let offset = bar.register as u64 + offset; + let val = pci_config_read_word(bar.bus, bar.slot, bar.func, offset as u8 & 0xFC); + let mut val = val.to_ne_bytes(); + val[offset.get_bits(0..2) as usize] = data; + pci_config_write_word(bar.bus, bar.slot, bar.func, offset as u8 & 0xFC, u32::from_ne_bytes(val)); + return; + }, + MappedBAR::Memory(MappedBARMemory { virt_addr, .. }) => *virt_addr, + MappedBAR::Memory64(MappedBARMemory64 { virt_addr, .. }) => *virt_addr, + }; + + // Handle Memory. + unsafe { + addr.wrapping_add(offset as usize).write_volatile(data) + } + } + + pub fn write_u16(&self, offset: u64, data: u16) { + // First, check the offset is within the size + assert!(offset.saturating_add(1) < self.size(), "Out of bound read: {} + 1 < {}", offset, self.size()); + + let addr = match self { + MappedBAR::Io(bar) => { + // Handle IO-Ports + let offset = bar.register as u64 + offset; + let val = pci_config_read_word(bar.bus, bar.slot, bar.func, offset as u8 & 0xFE); + let mut val = val.to_ne_bytes(); + BO::write_u16(&mut val[offset.get_bits(0..2) as usize..], data); + pci_config_write_word(bar.bus, bar.slot, bar.func, offset as u8 & 0xFC, u32::from_ne_bytes(val)); + return; + }, + MappedBAR::Memory(MappedBARMemory { virt_addr, .. }) => *virt_addr, + MappedBAR::Memory64(MappedBARMemory64 { virt_addr, .. }) => *virt_addr, + }; + + // Handle Memory. + unsafe { + (addr.wrapping_add(offset as usize) as *mut u16).write_volatile(data) + } + } + + pub fn write_u32(&self, offset: u64, data: u32) { + // First, check the offset is within the size + assert!(offset.saturating_add(3) < self.size(), "Out of bound read: {} + 3 < {}", offset, self.size()); + + let addr = match self { + MappedBAR::Io(bar) => { + // Handle IO-Ports + let offset = bar.register as u64 + offset; + let mut val = [0; 4]; + BO::write_u32(&mut val, data); + pci_config_write_word(bar.bus, bar.slot, bar.func, offset as u8 & 0xFC, u32::from_ne_bytes(val)); + return; + }, + MappedBAR::Memory(MappedBARMemory { virt_addr, .. }) => *virt_addr, + MappedBAR::Memory64(MappedBARMemory64 { virt_addr, .. }) => *virt_addr, + }; + + // Handle Memory. + unsafe { + (addr.wrapping_add(offset as usize) as *mut u32).write_volatile(data) + } + } +} + +impl PciDevice { + /// Checks if a device exists on given bus>slot>function. + /// + /// This is done by reading the Device ID - Vendor ID register (register 0). + /// If `0xFFFF_FFFF` is read back, this means that the device was non-existent, and we return None. + #[allow(clippy::absurd_extreme_comparisons)] + fn probe(bus: u8, slot: u8, function: u8) -> Option { + debug_assert!(bus <= MAX_BUS); + debug_assert!(slot <= MAX_SLOT); + debug_assert!(function <= MAX_FUNC); + let did_vid = pci_config_read_word(bus, slot, function, 0); + return if did_vid == 0xFFFF_FFFF { + None + } else { + Some(Self { + bus, + slot, + function, + did: (did_vid >> 16) as u16, + vid: did_vid as u16, + class: (pci_config_read_word(bus, slot, function, 8) >> 24) as u8, + subclass: (pci_config_read_word(bus, slot, function, 8) >> 16) as u8, + prog_if: (pci_config_read_word(bus, slot, function, 8) >> 8) as u8, + rev_id: pci_config_read_word(bus, slot, function, 8) as u8, + header_type: (pci_config_read_word(bus, slot, function, 12) >> 16) as u8, + latency_timer: (pci_config_read_word(bus, slot, function, 12) >> 8) as u8, + cache_line_size: pci_config_read_word(bus, slot, function, 12) as u8, + + header: read_header(bus, slot, function) + }) + }; + + /// Reads the remaining of the pci-registers, organising them based on header type. + fn read_header(bus: u8, slot: u8, function: u8) -> PciHeader { + // header_type, but bit 8 informs if device is multi-function, ignore it. + let header_type = (pci_config_read_word(bus, slot, function, 12) >> 16) as u8; + return match header_type & 0x7f { + 0x00 => PciHeader::GeneralDevice(GeneralPciHeader { + bars: decode_bars(bus, slot, function), + cardbus_cis_ptr: pci_config_read_word(bus, slot, function, 40), + subsystem_id: (pci_config_read_word(bus, slot, function, 44) >> 16) as u16, + subsystem_vendor_id: pci_config_read_word(bus, slot, function, 44) as u16, + expansion_rom_base_address: pci_config_read_word(bus, slot, function, 48), + capabilities_ptr: { + let mut cap = pci_config_read_word(bus, slot, function, 52) as u8; + // Get rid of the bottom 2 bits, they are reserved. + *cap.set_bits(0..2, 0) + }, + max_latency: (pci_config_read_word(bus, slot, function, 60) >> 24) as u8, + min_grant: (pci_config_read_word(bus, slot, function, 60) >> 16) as u8, + interrupt_pin: (pci_config_read_word(bus, slot, function, 60) >> 8) as u8, + interrupt_line: pci_config_read_word(bus, slot, function, 60) as u8, + }), + 0x01 => PciHeader::PCItoPCIBridge, + 0x02 => PciHeader::CardBus, + other => PciHeader::UnknownHeaderType(other) + }; + + /// Decode an u32 to BAR values. + fn decode_bar(bus: u8, slot: u8, function: u8, bar_num: u8) -> (u32, u32) { + let register = (bar_num + 4) * 4; + // read bar address + let addr = pci_config_read_word(bus, slot, function, register); + // write to get length + pci_config_write_word(bus, slot, function, register, 0xFFFF_FFFF); + // read back length + let length = pci_config_read_word(bus, slot, function, register); + // restore original value + pci_config_write_word(bus, slot, function, register, addr); + + (addr, length) + } + + fn decode_bars(bus: u8, slot: u8, function: u8) -> [Option; 6] { + let mut bar_num = 0; + let mut bars = [None; 6]; + while bar_num < 6 { + let bar = &mut bars[bar_num as usize]; + let (addr, length) = decode_bar(bus, slot, function, bar_num); + *bar = match (addr.get_bit(0), addr.get_bits(1..3)) { + (false, 0) => { + // memory space bar + Some(BAR::Memory(BARMemory { + phys_addr: addr & 0xFFFF_FFF0, + size: (!(length & 0xFFFF_FFF0)).wrapping_add(1) + })) + }, + (false, 2) => { + // memory space bar + bar_num += 1; + let (addrhigh, lengthhigh) = decode_bar(bus, slot, function, bar_num); + let addr = (addr as u64 & 0xFFFF_FFF0) | ((addrhigh as u64) << 32); + let length = (!((length as u64 & 0xFFFF_FFF0) | ((lengthhigh as u64) << 32))).wrapping_add(1); + Some(BAR::Memory64(BARMemory64 { + phys_addr: addr, + size: length + })) + }, + (true, _) => { + // io space bar + Some(BAR::Io(BARIo { + bus, slot, func: function, + register: addr & 0xFFFF_FFFC, + size: (!(length & 0xFFFF_FFFC)).wrapping_add(1), + })) + }, + _ => { + info!("Unsupported PCI BAR idx {} value {:#08x}", bar_num, addr); + None + } + }; + bar_num += 1; + } + bars + } + } + } + + /// Reads a configuration space register. + pub fn read_config_register(&self, register: u8) -> u32 { + pci_config_read_word(self.bus, self.slot, self.function, register) + } + + /// Writes to a configuration space register. + pub fn write_config_register(&self, register: u8, value: u32) { + pci_config_write_word(self.bus, self.slot, self.function, register, value) + } + + // register 1 + + /// Reads the status register. + pub fn status(&self) -> u16 { + (self.read_config_register(4) >> 16) as u16 + } + + /// Reads the command register. + /// + /// The Command register provides coarse control over a device's ability to + /// generate and respond to PCI cycles. + /// + /// See Chapter 6.2.2: Device Control. + fn command(&self) -> Command { + Command::from_bits_truncate((self.read_config_register(4) >> 0) as u16) + } + + /// Writes to the command register. + /// + /// The Command register provides coarse control over a device's ability to + /// generate and respond to PCI cycles. + /// + /// See Chapter 6.2.2: Device Control. + fn set_command(&self, command: Command) { + let mut config = self.read_config_register(4); + config.set_bits(0..16, command.bits() as u32); + self.write_config_register(4, config); + } + + pub fn capabilities(&self) -> impl Iterator { + let mut capabilities_ptr = 0; + if self.status().get_bit(4) { + if let PciHeader::GeneralDevice(device) = self.header { + capabilities_ptr = device.capabilities_ptr + } + } + + capabilities::CapabilitiesIter::new(self, capabilities_ptr) + } + + pub fn enable_msix(&self, val: bool) -> Result<(), ()> { + let msix = self.capabilities().find(|v| if let Capability::MsiX(..) = v { true } else { false }); + if let Some(Capability::MsiX(msix)) = msix { + // Enable Bus Mastering, necessary for MsiX to work. + self.set_command(self.command() | Command::BUS_MASTER | Command::MEMORY_SPACE | Command::IO_SPACE); + msix.enable_msix(val); + Ok(()) + } else { + Err(()) + } + } + + pub fn msix_table_size(&self) -> Result { + let msix = self.capabilities().find(|v| if let Capability::MsiX(..) = v { true } else { false }); + if let Some(Capability::MsiX(msix)) = msix { + Ok(msix.table_size()) + } else { + Err(()) + } + } + + pub fn set_msix_message_entry(&self, entry: usize, val: capabilities::MsiXEntry) -> Result<(), ()> { + let msix = self.capabilities().find(|v| if let Capability::MsiX(..) = v { true } else { false }); + if let Some(Capability::MsiX(msix)) = msix { + msix.set_message_entry(entry, val); + Ok(()) + } else { + Err(()) + } + } +} + +/// Read one of the 64 32-bit registers of a pci bus>device>func. +/// Register is a byte offset. It should be aligned to 4. +#[allow(clippy::absurd_extreme_comparisons)] +fn pci_config_read_word(bus: u8, slot: u8, func: u8, register: u8) -> u32 { + debug_assert!(bus <= MAX_BUS); + debug_assert!(slot <= MAX_SLOT); + debug_assert!(func <= MAX_FUNC); + debug_assert!(register <= MAX_REGISTER); + debug_assert!(register & 0x3 == 0); + + let lbus = u32::from(bus); + let lslot = u32::from(slot); + let lfunc = u32::from(func); + let lregister = u32::from(register); + let mut ports = PCI_CONFIG_PORTS.lock(); + + /* create the configuration address */ + let address: u32 = (lbus << 16) | (lslot << 11) | + (lfunc << 8) | (lregister & 0xFC) | 0x80000000; + + /* write out the address */ + ports.address.write(address); + + /* read the data */ + ports.data.read() +} + +/// Write one of the 64 32-bit registers of a pci bus>device>func. +/// Register is a byte offset. It should be aligned to 4. +#[allow(clippy::absurd_extreme_comparisons)] +fn pci_config_write_word(bus: u8, slot: u8, func: u8, register: u8, value: u32) { + debug_assert!(bus <= MAX_BUS); + debug_assert!(slot <= MAX_SLOT); + debug_assert!(func <= MAX_FUNC); + debug_assert!(register <= MAX_REGISTER); + debug_assert!(register & 0x3 == 0); + + let lbus = u32::from(bus); + let lslot = u32::from(slot); + let lfunc = u32::from(func); + let lregister = u32::from(register); + let mut ports = PCI_CONFIG_PORTS.lock(); + + /* create the configuration address */ + let address: u32 = (lbus << 16) | (lslot << 11) | + (lfunc << 8) | (lregister & 0xFC) | 0x80000000; + + /* write out the address */ + ports.address.write(address); + + /* read the data */ + ports.data.write(value) +} + +/// Iterator created with the [discover] function. +// First u8 is bus, second is slot, third is func. +#[derive(Debug)] +struct PciDeviceIterator(u8, u8, u8); + +impl Iterator for PciDeviceIterator { + type Item = PciDevice; + fn next(&mut self) -> Option { + for bus in self.0..MAX_BUS { + for slot in self.1..MAX_SLOT { + // test function 0. + if let Some(device) = PciDevice::probe(bus, slot, 0) { + let is_multifunction = device.header_type & 0x80 != 0; + if self.2 == 0 { + self.2 += 1; + return Some(device); + } + // check for other function on the same device + if is_multifunction { + for function in self.2..MAX_FUNC { + self.2 += 1; + if let Some(device) = PciDevice::probe(bus, slot, function) { + return Some(device); + } + } + } + } + self.2 = 0; + self.1 += 1; + } + self.2 = 0; + self.1 = 0; + self.0 += 1; + } + return None; + } +} + +/// Discover all pci devices, by probing the PID-VID of every slot on every bus. +/// +/// A device is discovered when its `(bus, slot, function 0x00)[register 0x00] != 0xFFFF_FFFF`. +/// Then, an additional [PciDevice] will be returned for every of its other functions that also +/// return anything different from `0xFFFF_FFFF`. +pub fn discover() -> impl Iterator + core::fmt::Debug { + PciDeviceIterator(0, 0, 0) +} + diff --git a/libuser/src/pci/capabilities.rs b/libuser/src/pci/capabilities.rs new file mode 100644 index 000000000..6f3289150 --- /dev/null +++ b/libuser/src/pci/capabilities.rs @@ -0,0 +1,181 @@ +use crate::pci::{PciDevice, PciHeader}; +use crate::pci::{pci_config_read_word, pci_config_write_word}; +use crate::error::KernelError; +use bit_field::BitField; +use byteorder::LE; + +pub(super) struct CapabilitiesIter<'a> { + device: &'a PciDevice, + offset: u8, +} + +impl<'a> Iterator for CapabilitiesIter<'a> { + type Item = Capability<'a>; + fn next(&mut self) -> Option> { + if self.offset == 0 { + return None + } + info!("Reading capability at {:#02x}", self.offset); + let (cap, next) = Capability::parse(self.device, self.offset); + self.offset = next; + Some(cap) + } +} + +impl<'a> CapabilitiesIter<'a> { + pub(super) fn new(device: &'a PciDevice, offset: u8) -> CapabilitiesIter<'a> { + CapabilitiesIter { device, offset } + } +} + +#[derive(Debug)] // TODO: More interesting debug. +pub struct MsiX<'a> { + inner: RWCapability<'a> +} + +#[repr(C)] +#[derive(Copy, Clone, Debug)] +pub struct MsiXEntry { + pub addr: u64, + pub data: u32, + pub ctrl: MsiXControl +} + +bitfield! { + #[derive(Clone, Copy)] + pub struct MsiXControl(u32); + impl Debug; + pub masked, set_masked: 0; +} + +impl<'a> MsiX<'a> { + pub fn enable_msix(&self, val: bool) { + let mut val = self.inner.read_u32(0); + val.set_bit(31, true); + self.inner.write_u32(0, val); + } + + pub fn table_size(&self) -> usize { + self.inner.read_u32(0).get_bits(16..27) as usize + 1 + } + + pub fn set_message_entry(&self, entry: usize, val: MsiXEntry) -> Result<(), KernelError> { + assert!(entry < self.table_size()); + let mut table_offset_bir = self.inner.read_u32(4); + let bir = table_offset_bir.get_bits(0..3); + let table_offset = *table_offset_bir.set_bits(0..3, 0) as u64; + + let PciDevice { bus, slot, function, .. } = self.inner.device; + + let header = match self.inner.device.header { + PciHeader::GeneralDevice(header) => header, + _ => unreachable!() + }; + + let bar = header.bar(bir as usize).expect(&format!("Device {}.{}.{} to contain BAR {}", bus, slot, function, bir)); + let mapped_bar = bar.map()?; + + mapped_bar.write_u32::(table_offset + (entry * 16) as u64, val.addr.get_bits(0..32) as u32); + mapped_bar.write_u32::(table_offset + (entry * 16 + 4) as u64, val.addr.get_bits(32..64) as u32); + mapped_bar.write_u32::(table_offset + (entry * 16 + 8) as u64, val.data); + mapped_bar.write_u32::(table_offset + (entry * 16 + 12) as u64, val.ctrl.0); + + Ok(()) + } +} + +#[derive(Debug)] +pub struct RWCapability<'a> { + device: &'a PciDevice, + offset: u8, +} + +impl<'a> RWCapability<'a> { + pub fn read_u32(&self, offset: u8) -> u32 { + pci_config_read_word(self.device.bus, self.device.slot, self.device.function, (self.offset + offset) & 0xFC) + } + pub fn write_u32(&self, offset: u8, value: u32) { + pci_config_write_word(self.device.bus, self.device.slot, self.device.function, (self.offset + offset) & 0xFC, value); + } +} + +#[derive(Debug)] +pub enum Capability<'a> { + Reserved, + PciPowerManagement, + AcceleratedGraphicsPort, + VitalProductData, + SlotIdentification, + Msi, + CompactPciHotSwap, + PciX, + HyperTransport, + VendorSpecific(RWCapability<'a>, u8), + DebugPort, + CompactPciCentralResourceControl, + PciHotPlug, + AcceleratedGraphicsPort8x, + SecureDevice, + PciExpress, + MsiX(MsiX<'a>), + Unknown(u8), +} + +/*// TODO: Lazily read the Vendor-Specific Capabilities? +// BODY: Currently, vendor-specific capabilities are eagerly read and kept in a +// BODY: vector. This is likely suboptimal. Ideally, we should get some type that +// BODY: we can call functions on to get u32s from. +let mut size = word.get_bits(16..24) as u8; +let mut data = Vec::with_capacity(size.saturating_sub(3) as usize); + +let mut idx = 4u8; +data.push(word.get_bits(24..32) as u8); +while idx < size { + let word = pci_config_read_word(bus, slot, function, register + idx); + for i in 0..core::cmp::min(size - idx, 4) { + let i = i as usize; + data.push(word.get_bits(i * 8..(i + 1) * 8) as u8); + } + idx += 4; +}*/ +impl<'a> Capability<'a> { + pub fn parse(device: &'a PciDevice, register: u8) -> (Capability<'a>, u8) { + let word = pci_config_read_word(device.bus, device.slot, device.function, register); + let ty = word.get_bits(0..8) as u8; + let mut next = word.get_bits(8..16) as u8; + // 6.7: Get rid of the lower 2 bits, they are reserved for future use. + next.set_bits(0..2, 0); + + let rw_cap = RWCapability { + device, offset: register + }; + + let cap = match ty { + 0x00 => Capability::Reserved, + 0x01 => Capability::PciPowerManagement, + 0x02 => Capability::AcceleratedGraphicsPort, + 0x03 => Capability::VitalProductData, + 0x04 => Capability::SlotIdentification, + 0x05 => Capability::Msi, + 0x06 => Capability::CompactPciHotSwap, + 0x07 => Capability::PciX, + 0x08 => Capability::HyperTransport, + 0x09 => { + let len = word.get_bits(16..24) as u8; + Capability::VendorSpecific(rw_cap, len) + }, + 0x0A => Capability::DebugPort, + 0x0B => Capability::CompactPciCentralResourceControl, + 0x0C => Capability::PciHotPlug, + 0x0E => Capability::AcceleratedGraphicsPort8x, + 0x0F => Capability::SecureDevice, + 0x10 => Capability::PciExpress, + 0x11 => Capability::MsiX(MsiX { + inner: rw_cap + }), + id => Capability::Unknown(id) + }; + + (cap, next) + } +} diff --git a/libuser/src/syscalls.rs b/libuser/src/syscalls.rs index 6b3fc0482..863d98e95 100644 --- a/libuser/src/syscalls.rs +++ b/libuser/src/syscalls.rs @@ -439,3 +439,22 @@ pub fn map_mmio_region(physical_address: usize, size: usize, virtual_address: us Ok(()) } } + +pub fn get_system_tick() -> u64 { + let mut registers = Registers { + eax: nr::GetSystemTick, + ebx: 0, + ecx: 0, + edx: 0, + esi: 0, + edi: 0, + ebp: 0, + }; + + unsafe { + // Safety: Syscall to GetSystemTick is always safe. + syscall_inner(&mut registers); + } + + registers.eax as u64 | ((registers.ebx as u64) << 32) +} \ No newline at end of file diff --git a/libuser/src/types.rs b/libuser/src/types.rs index a69fd6cc2..717742146 100644 --- a/libuser/src/types.rs +++ b/libuser/src/types.rs @@ -107,6 +107,19 @@ impl ClientSession { .map_err(|v| v.into()) } + pub fn query_pointer_buffer(&self) -> Result { + // Use a very small buffer to avoid stackoverflows - we really don't + // need a big one here anyways. + let mut data = [0; 0x10]; + let mut msg = Message::<(), [_; 0], [_; 0], [_; 0]>::new_request(None, 3); + msg.set_ty(MessageTy::Control); + msg.pack(&mut data[..]); + self.send_sync_request_with_user_buffer(&mut data[..])?; + let msg = Message::::unpack(&data[..]); + msg.error()?; + Ok(msg.raw()) + } + /// Consumes the session, returning the underlying handle. Note that closing /// a Handle without sending a close IPC message will leak the object in the /// sysmodule. You should always reconstruct the ClientSession from the diff --git a/swipc-gen/src/gen_rust_code.rs b/swipc-gen/src/gen_rust_code.rs index 490eedb9e..d2333453e 100644 --- a/swipc-gen/src/gen_rust_code.rs +++ b/swipc-gen/src/gen_rust_code.rs @@ -133,11 +133,11 @@ fn format_args(args: &[(Alias, Option)], ret: &[(Alias, Option)] /// arguments. /// /// See [get_type] to find the mapping of a SwIPC type to a Rust type. -fn format_ret_ty(ret: &[(Alias, Option)]) -> Result { +fn format_ret_ty(ret: &[(Alias, Option)], server: bool) -> Result { let mut v = Vec::new(); for (ty, _name) in named_iterator(ret, true) { - v.push(get_type(true, ty, false)?); + v.push(get_type(true, ty, server)?); } match v.len() { @@ -217,6 +217,8 @@ fn get_type(output: bool, ty: &Alias, is_server: bool) -> Result Alias::Handle(is_copy, ty) => if let Some(s) = get_handle_type(ty) { Ok(format!("{}{}", if *is_copy && !is_server && !output { "&" } else { "" }, s)) + } else if *is_copy && is_server && output { + Ok("sunrise_libuser::types::HandleRef<'static>".to_string()) } else { Ok(format!("sunrise_libuser::types::{}", if *is_copy && !is_server && !output { "HandleRef" } else { "Handle" })) }, @@ -270,7 +272,7 @@ fn format_cmd(cmd: &Func) -> Result { for line in cmd.doc.lines() { writeln!(s, " /// {}", line).unwrap(); } - writeln!(s, " pub fn {}(&mut self, {}) -> Result<{}, Error> {{", &cmd.name, format_args(&cmd.args, &cmd.ret, false)?, format_ret_ty(&cmd.ret)?).unwrap(); + writeln!(s, " pub fn {}(&mut self, {}) -> Result<{}, Error> {{", &cmd.name, format_args(&cmd.args, &cmd.ret, false)?, format_ret_ty(&cmd.ret, false)?).unwrap(); writeln!(s, " use sunrise_libuser::ipc::Message;").unwrap(); writeln!(s, " let mut buf__ = [0; 0x100];").unwrap(); writeln!(s).unwrap(); @@ -317,9 +319,9 @@ fn format_cmd(cmd: &Func) -> Result { // C Buffer (2, 2, false) => writeln!(s, " msg__.push_in_pointer({}, {});", argname, !ty.get_bit(4)).unwrap(), // Smart A+X - (1, 0, true) => return Err(Error::UnsupportedStruct), + (1, 0, true) => writeln!(s, " msg__.push_out_smart_pointer(self.1, {});", argname).unwrap(), // Smart B+C - (2, 0, true) => return Err(Error::UnsupportedStruct), + (2, 0, true) => writeln!(s, " msg__.push_in_smart_pointer(self.1, {});", argname).unwrap(), _ => panic!("Illegal buffer type: {}", ty) } }, @@ -334,9 +336,9 @@ fn format_cmd(cmd: &Func) -> Result { // C Buffer (2, 2, false) => writeln!(s, " msg__.push_in_pointer({}, {});", argname, !ty.get_bit(4)).unwrap(), // Smart A+X - (1, 0, true) => return Err(Error::UnsupportedStruct), + (1, 0, true) => writeln!(s, " msg__.push_out_smart_pointer(self.1, {});", argname).unwrap(), // Smart B+C - (2, 0, true) => return Err(Error::UnsupportedStruct), + (2, 0, true) => writeln!(s, " msg__.push_in_smart_pointer(self.1, {});", argname).unwrap(), _ => panic!("Illegal buffer type: {}", ty) } }, @@ -513,17 +515,34 @@ fn gen_call(cmd: &Func) -> Result { { match item { Alias::Array(_, bufty) | Alias::Buffer(_, bufty, _) => { - let (ismut,direction, ty) = match (bufty.get_bits(0..2), bufty.get_bits(2..4)) { - (0b01, 0b01) => ("", "in", "buffer"), - (0b01, 0b10) => ("", "in", "pointer"), - (0b10, 0b01) => ("mut", "out", "buffer"), - (0b10, 0b10) => ("mut", "out", "pointer"), - _ => panic!("Invalid bufty") + let (ismut,direction, ty) = match (bufty.get_bits(0..2), bufty.get_bits(2..4), bufty.get_bit(5)) { + (0b01, _, true) => ("", "in", "smart_buffer"), + (0b10, _, true) => ("mut", "out", "smart_buffer"), + (0b01, 0b01, false) => ("", "in", "buffer"), + (0b01, 0b10, false) => ("", "in", "pointer"), + (0b10, 0b01, false) => ("mut", "out", "buffer"), + (0b10, 0b10, false) => ("mut", "out", "pointer"), + (direction, ty, smart) => panic!("Invalid bufty while handling {:?}: {:x}, {:x} {} {:x}", cmd, direction, ty, smart, bufty) }; - args += &format!("&{} *msg__.pop_{}_{}().unwrap(), ", ismut, direction, ty); + let realty = get_type(false, item, false)?; + let realty = if realty.starts_with("&mut") { + realty.trim_start_matches("&mut") + } else if realty.starts_with("&") { + realty.trim_start_matches("&") + } else { + &*realty + }; + + // TODO: Make pop_out_buffer and co safe to call. + // BODY: Currently, pop_out_buffer (and other functions of + // BODY: that family) are unsafe to call as they basically + // BODY: allow transmuting variables. We should use a crate + // BODY: like `plain` to ensure that said functions are only + // BODY: callable when it is safe. + args += &format!("unsafe {{ &{} *msg__.pop_{}_{}::<{}>().unwrap() }}, ", ismut, direction, ty, realty); }, Alias::Object(ty) => { - args += &format!("{}Proxy(sunrise_libuser::types::ClientSession(msg__.pop_handle_move().unwrap())), ", ty); + args += &format!("{}Proxy::from(sunrise_libuser::types::ClientSession(msg__.pop_handle_move().unwrap())), ", ty); }, Alias::Handle(is_copy, ty) => { let handle = if *is_copy { @@ -564,7 +583,7 @@ fn gen_call(cmd: &Func) -> Result { writeln!(s, " match ret__ {{").unwrap(); writeln!(s, " Ok(ret) => {{").unwrap(); - let retcount = named_iterator(&cmd.ret, false).count(); + let retcount = named_iterator(&cmd.ret, true).count(); for (idx, (item, _)) in named_iterator(&cmd.ret, true).enumerate().filter(|(_, (ty, _))| !is_raw(ty)) { let ret = if retcount == 1 { @@ -578,14 +597,14 @@ fn gen_call(cmd: &Func) -> Result { }, Alias::Handle(is_copy, ty) => { let (is_ref, handle) = if *is_copy { - ("&", "copy") + (".as_ref()", "copy") } else { ("", "move") }; match (get_handle_type(ty), ty) { - (_, Some(HandleType::ClientSession)) => writeln!(s, "msg__.push_handle_{}({}({}).into_handle());", handle, is_ref, ret).unwrap(), - (Some(_), _) => writeln!(s, "msg__.push_handle_{}({}({}).0);", handle, is_ref, ret).unwrap(), - _ => writeln!(s, "msg__.push_handle_{}({}{})", handle, is_ref, ret).unwrap(), + (_, Some(HandleType::ClientSession)) => writeln!(s, "msg__.push_handle_{}(({}).into_handle(){});", handle, ret, is_ref).unwrap(), + (Some(_), _) => writeln!(s, "msg__.push_handle_{}(({}).0{});", handle, ret, is_ref).unwrap(), + _ => writeln!(s, "msg__.push_handle_{}({});", handle, ret).unwrap(), }; }, Alias::Pid => { @@ -595,9 +614,9 @@ fn gen_call(cmd: &Func) -> Result { } } - if raw_iterator(&cmd.ret, false).count() > 0 { - if named_iterator(&cmd.ret, false).count() == 1 { - let (_, name) = raw_iterator(&cmd.ret, false).next().unwrap(); + if raw_iterator(&cmd.ret, true).count() > 0 { + if named_iterator(&cmd.ret, true).count() == 1 { + let (_, name) = raw_iterator(&cmd.ret, true).next().unwrap(); writeln!(s, "msg__.push_raw({} {{ {}: ret }});", out_raw, name).unwrap(); } else { writeln!(s, "msg__.push_raw({} {{", out_raw).unwrap(); @@ -631,7 +650,7 @@ pub fn generate_trait(ifacename: &str, interface: &Interface) -> String { } writeln!(s, "pub trait {} {{", trait_name).unwrap(); for cmd in &interface.funcs { - match format_args(&cmd.args, &cmd.ret, true).and_then(|v| format_ret_ty(&cmd.ret).map(|u| (v, u))) { + match format_args(&cmd.args, &cmd.ret, true).and_then(|v| format_ret_ty(&cmd.ret, true).map(|u| (v, u))) { Ok((args, ret)) => { for line in cmd.doc.lines() { writeln!(s, "/// {}", line).unwrap(); @@ -680,7 +699,14 @@ pub fn generate_proxy(ifacename: &str, interface: &Interface) -> String { writeln!(s, "/// {}", line).unwrap(); } writeln!(s, "#[derive(Debug)]").unwrap(); - writeln!(s, "pub struct {}(ClientSession);", struct_name).unwrap(); + // Detect the presence of smart buffers on this interface. If there is one + // present, we want to cache the pointer buffer size in the structure. + let has_smart_buffer = interface.funcs.iter().any(|cmd| cmd.args.iter().chain(cmd.ret.iter()).any(|(arg, _)| if let Alias::Buffer(_, ty, _) | Alias::Array(_, ty) = arg { ty.get_bit(5) } else { false } )); + if has_smart_buffer { + writeln!(s, "pub struct {}(ClientSession, u16);", struct_name).unwrap(); + } else { + writeln!(s, "pub struct {}(ClientSession);", struct_name).unwrap(); + } writeln!(s).unwrap(); writeln!(s, "impl From<{}> for ClientSession {{", struct_name).unwrap(); writeln!(s, " fn from(sess: {}) -> ClientSession {{", struct_name).unwrap(); @@ -690,7 +716,17 @@ pub fn generate_proxy(ifacename: &str, interface: &Interface) -> String { writeln!(s).unwrap(); writeln!(s, "impl From for {} {{", struct_name).unwrap(); writeln!(s, " fn from(sess: ClientSession) -> {} {{", struct_name).unwrap(); - writeln!(s, " {}(sess)", struct_name).unwrap(); + + // Cache the pointer buffer size in the structure only if we have smart + // buffers on the interface. + if has_smart_buffer { + // Assume that if there's an error, the remote doesn't support the + // pointer buffer at all. + writeln!(s, " let pointer_buffer = sess.query_pointer_buffer().unwrap_or(0);").unwrap(); + writeln!(s, " {}(sess, pointer_buffer)", struct_name).unwrap(); + } else { + writeln!(s, " {}(sess)", struct_name).unwrap(); + } writeln!(s, " }}").unwrap(); writeln!(s, "}}").unwrap(); @@ -715,7 +751,7 @@ pub fn generate_proxy(ifacename: &str, interface: &Interface) -> String { let mut service_name = service.to_string(); service_name += &"\\0"; writeln!(s, r#" let _ = match syscalls::connect_to_named_port("{}") {{"#, service_name).unwrap(); - writeln!(s, " Ok(s) => return Ok({}(s)),", struct_name).unwrap(); + writeln!(s, " Ok(s) => return Ok({}::from(s)),", struct_name).unwrap(); writeln!(s, " Err(KernelError::NoSuchEntry) => syscalls::sleep_thread(0),").unwrap(); writeln!(s, " Err(err) => Err(err)?").unwrap(); writeln!(s, " }};").unwrap(); @@ -731,7 +767,7 @@ pub fn generate_proxy(ifacename: &str, interface: &Interface) -> String { writeln!(s, r#" core::mem::transmute(*b"{}")"#, service_name).unwrap(); writeln!(s, " }};").unwrap(); writeln!(s, " let _ = match sunrise_libuser::sm::IUserInterfaceProxy::raw_new()?.get_service(svcname) {{").unwrap(); - writeln!(s, " Ok(s) => return Ok({}(s)),", struct_name).unwrap(); + writeln!(s, " Ok(s) => return Ok({}::from(s)),", struct_name).unwrap(); writeln!(s, " Err(Error::Sm(SmError::ServiceNotRegistered, ..)) => syscalls::sleep_thread(0),").unwrap(); writeln!(s, " Err(err) => return Err(err)").unwrap(); writeln!(s, " }};").unwrap(); diff --git a/virtio/Cargo.toml b/virtio/Cargo.toml new file mode 100644 index 000000000..4e54fe76b --- /dev/null +++ b/virtio/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "sunrise-virtio" +version = "0.1.0" +authors = ["roblabla "] +edition = "2018" + +[dependencies] +sunrise-libuser = { path = "../libuser" } +log = "0.4" +bit_field = "0.9.0" +bitflags = "1.0.4" +bitfield = "0.13" + +[dependencies.static_assertions] +version = "0.3.1" +features = ["nightly"] + +[dependencies.byteorder] +default-features = false +version = "1.3.1" + +[dependencies.smoltcp] +git = "https://github.com/roblabla/smoltcp" +default-features = false +features = ["alloc", "proto-ipv4", "proto-igmp", "proto-ipv6", "socket-raw", "socket-icmp", "socket-udp", "socket-tcp", "log"] \ No newline at end of file diff --git a/virtio/src/main.rs b/virtio/src/main.rs new file mode 100755 index 000000000..2fd105277 --- /dev/null +++ b/virtio/src/main.rs @@ -0,0 +1,392 @@ +//! Virtio Driver +//! +//! This binary contains the common bits of a virtio driver. It implements the +//! [virtio spec 1.1](https://web.archive.org/web/20190628162805/https://docs.oasis-open.org/virtio/virtio/v1.1/virtio-v1.1.html). +//! +//! It does **not** support legacy or transitional interfaces. Only the modern +//! interfaces are implemented. + +#![no_std] +#![feature(underscore_const_names, slice_concat_ext)] + +#[macro_use] +extern crate alloc; + +use sunrise_libuser::capabilities; +use sunrise_libuser::syscalls; +use sunrise_libuser::types::ReadableEvent; +use sunrise_libuser::pci::{discover as pci_discover, PciHeader, PciDevice, + GeneralPciHeader, + CONFIG_ADDRESS as PCI_CONFIG_ADDRESS, + CONFIG_DATA as PCI_CONFIG_DATA}; +use sunrise_libuser::pci::capabilities::{MsiXEntry, MsiXControl, Capability}; + +use sunrise_libuser::error::{VirtioError, Error}; +use log::*; +use bitflags::bitflags; +use crate::pci::{CommonCfg, NotificationCfg, Config}; +use bitfield::bitfield; +use virtqueue::VirtQueue; +use core::sync::atomic::{fence, Ordering}; +use alloc::vec::Vec; + +mod pci; +mod net; +mod virtqueue; + +bitflags! { + /// 2.1: Device Status field + /// + /// The device status field provides a simple low-level indication of the completed steps of the + /// device initialization sequence (specified in chapter 3.1). + struct DeviceStatus: u8 { + /// Indicates that the guest OS has found the device and recognized it as a valid virtio + /// device. + const ACKNOWLEDGE = 1; + /// Indicates that the guest OS knows how to drive the device. + const DRIVER = 2; + /// Indicates that something went wrong with the guest, and it has given up on the device. + /// This could be an internal error, or the driver didn't like the device for some reason, + /// or even a fatal error during device operation. + const FAILED = 128; + /// Indicates that the driver has acknowledged all the features it understands, and feature + /// negociation is complete. + const FEATURES_OK = 8; + /// Indicates that the driver is set up and ready to drive the device. + const DRIVER_OK = 4; + /// Indicates that the device has experienced an error from which it can't recover. + const DEVICE_NEEDS_RESET = 64; + } +} + +bitflags! { + /// 6: Reserved Feature Bits + struct CommonFeatures: u64 { + /// Negotiating this feature indicates that the driver can use + /// descriptors with the VIRTQ_DESC_F_INDIRECT flag set, as described in + /// 2.6.5.3 Indirect Descriptors and 2.7.7 Indirect Flag: Scatter-Gather + /// Support. + const RING_INDIRECT_DESC = 1 << 28; + /// This feature enables the used_event and the avail_event fields as + /// described in 2.6.7, 2.6.8 and 2.7.10. + const RING_EVENT_IDX = 1 << 29; + /// This indicates compliance with this specification (Virtio 1.1), + /// giving a simple way to detect legacy devices or drivers. + const VERSION_1 = 1 << 32; + /// This feature indicates that the device can be used on a platform + /// where device access to data in memory is limited and/or translated. + /// E.g. this is the case if the device can be located behind an IOMMU + /// that translates bus addresses from the device into physical addresses + /// in memory, if the device can be limited to only access certain memory + /// addresses or if special commands such as a cache flush can be needed + /// to synchronise data in memory with the device. Whether accesses are + /// actually limited or translated is described by platform-specific + /// means. If this feature bit is set to 0, then the device has same + /// access to memory addresses supplied to it as the driver has. In + /// particular, the device will always use physical addresses matching + /// addresses used by the driver (typically meaning physical addresses + /// used by the CPU) and not translated further, and can access any + /// address supplied to it by the driver. When clear, this overrides any + /// platform-specific description of whether device access is limited or + /// translated in any way, e.g. whether an IOMMU may be present. + // NOTE: If this flag is not negociated, either the device becomes a + // backdoor, or it becomes unusable... It might be a good idea to find + // out which. + const ACCESS_PLATFORM = 1 << 33; + /// This feature indicates support for the packed virtqueue layout as + /// described in 2.7 Packed Virtqueues. + const RING_PACKED = 1 << 34; + /// This feature indicates that all buffers are used by the device in the + /// same order in which they have been made available. + const IN_ORDER = 1 << 35; + /// This feature indicates that memory accesses by the driver and the + /// device are ordered in a way described by the platform. + /// + /// If this feature bit is negotiated, the ordering in effect for any + /// memory accesses by the driver that need to be ordered in a specific + /// way with respect to accesses by the device is the one suitable for + /// devices described by the platform. This implies that the driver needs + /// to use memory barriers suitable for devices described by the + /// platform; e.g. for the PCI transport in the case of hardware PCI + /// devices. + /// + /// If this feature bit is not negotiated, then the device and driver are + /// assumed to be implemented in software, that is they can be assumed to + /// run on identical CPUs in an SMP configuration. Thus a weaker form of + /// memory barriers is sufficient to yield better performance. + const ORDER_PLATFORM = 1 << 36; + /// This feature indicates that the device supports Single Root I/O + /// Virtualization. Currently only PCI devices support this feature. + const SR_IOV = 1 << 37; + /// This feature indicates that the driver passes extra data (besides + /// identifying the virtqueue) in its device notifications. See 2.7.23 + /// Driver notifications. + const NOTIFICATION_DATA = 1 << 38; + } +} + +bitfield! { + pub struct Notification(u32); + impl Debug; + /// Virtqueue number to be notified. + virtqueue_idx, set_virtqueue_idx: 15, 0; + /// Offset within the ring where the next available ring entry will be + /// written. When VIRTIO_F_RING_PACKED has been negotiated this refers to the + /// offset (in units of descriptor entries) within the descriptor ring where + /// the next available descriptor will be written. + next_off_packed, set_next_off_packed: 30, 16; + /// Wrap Counter. With VIRTIO_F_RING_PACKED this is the wrap counter + /// referring to the next available descriptor. + next_wrap_packed, set_next_wrap_packed: 31; + /// Offset within the ring where the next available ring entry will be + /// written. When VIRTIO_F_RING_PACKED has not been negotiated this refers to + /// the available index. + next_off_split, set_next_off_split: 31, 16; +} + + +#[derive(Debug)] +pub struct VirtioDevice { + virtio_did: u16, + common_features: CommonFeatures, + device: PciDevice, + header: GeneralPciHeader, + common_cfg: CommonCfg, + notif_cfg: NotificationCfg, + device_cfg: Option, + queues: Vec>, + irq_event: ReadableEvent, +} + +impl VirtioDevice { + /// 3.1: Device Initialization + pub fn acknowledge(&mut self) { + self.reset(); + self.common_cfg.set_device_status(DeviceStatus::ACKNOWLEDGE); + + // Setup MSI-X vector. + self.device.enable_msix(true).unwrap(); + let mut entry = MsiXEntry { + // TODO: DMAR + addr: 0xFEE0_0000, + data: 0x0000_0033, + ctrl: MsiXControl(0) + }; + self.device.set_msix_message_entry(0, entry).unwrap(); + + self.queues.clear(); + for i in 0..self.common_cfg.num_queues() { + self.queues.push(None) + } + } + + /// 4.1.4.3: Writing a 0 to device status resets the device. + pub fn reset(&mut self) { + self.common_cfg.set_device_status(DeviceStatus::empty()); + while !self.common_cfg.device_status().is_empty() { + // TODO: Schedule out? + } + } + + /// 4.1.5.1.3 Virtqueue Configuration + pub fn setup_virtqueue(&mut self, virtqueue_idx: u16) { + let mut queue = self.common_cfg.queue(virtqueue_idx); + let size = queue.size; + let virtqueue = VirtQueue::new(size); + queue.desc = virtqueue.descriptor_area_dma_addr(); + queue.driver = virtqueue.driver_area_dma_addr(); + queue.device = virtqueue.device_area_dma_addr(); + queue.msix_vector = 0; + queue.enable = true; + self.common_cfg.set_queue(virtqueue_idx, &queue); + self.queues[virtqueue_idx as usize] = Some(virtqueue); + } + + /// Negociate common features. + pub fn negociate_features(&mut self, supported_features: u64, required_features: u64, preconditions: fn(u64) -> bool) -> Result { + let device_features = self.common_cfg.device_feature_bits(); + + let required_virtio_features = CommonFeatures::VERSION_1 /*| CommonFeatures::ACCESS_PLATFORM*/; + + let required_features = required_virtio_features.bits() | required_features; + + let supported_features = supported_features | required_features; + + let common_features = device_features & supported_features; + + if common_features & required_features != required_features { + info!("Required features not set: {:x}", !common_features & required_features); + self.common_cfg.set_device_status(DeviceStatus::FAILED); + Err(VirtioError::FeatureNegociationFailed.into()) + } else if !preconditions(common_features) { + info!("Preconditions not met for features: {:x}", common_features); + self.common_cfg.set_device_status(DeviceStatus::FAILED); + Err(VirtioError::FeatureNegociationFailed.into()) + } else { + self.common_cfg.set_driver_features(common_features); + self.common_cfg.set_device_status(DeviceStatus::FEATURES_OK); + if self.common_cfg.device_status().contains(DeviceStatus::FEATURES_OK) { + self.common_features = CommonFeatures::from_bits_truncate(common_features); + Ok(common_features) + } else { + info!("Device refused our feature set! {:x}", common_features); + self.common_cfg.set_device_status(DeviceStatus::FAILED); + Err(VirtioError::FeatureNegociationFailed.into()) + } + } + } + + pub fn acquire_device_cfg(&mut self) -> Config { + self.device_cfg.take().unwrap() + } + + pub fn notify(&self, vq: u16) { + if let Some(queue) = self.queues.get(vq as usize).and_then(|v| v.as_ref()) { + // 2.6.13.6: The driver performs a suitable memory barrier to ensure + // that it updates the idx field before checking for notification + // suppression. + fence(Ordering::SeqCst); + + if !queue.device_notif_suppressed() { + let queue_notify_off = self.common_cfg.queue_notify_off(vq) as usize; + if self.common_features.contains(CommonFeatures::NOTIFICATION_DATA) { + debug!("Notifying {}", vq); + let mut notif = Notification(0); + notif.set_virtqueue_idx(vq.into()); + notif.set_next_off_split(queue.get_available_idx().into()); + self.notif_cfg.notify_with_notification(queue_notify_off as usize, notif); + } else { + debug!("Notifying {}", vq); + self.notif_cfg.notify_with_virtqueue(queue_notify_off as usize, vq); + } + } else { + debug!("Notifications for {} suppressed", vq); + } + } else { + error!("Queue {} does not exist", vq); + } + } + + pub fn queues(&mut self) -> &mut [Option] { + &mut self.queues[..] + } +} + +mod ping; + +fn main() { + debug!("Virtio driver starting up"); + unsafe { + let mapping : *mut [u8; 0x1000] = sunrise_libuser::mem::map_mmio(0xfe003000).unwrap(); + (*mapping)[4] = 1; + } + + let virtio_devices = pci_discover() + .filter(|device| device.vid() == 0x1AF4 && 0x1000 <= device.did() && device.did() <= 0x107F) + ; + + let mut devices = Vec::new(); + + for device in virtio_devices { + let header = match device.header() { + PciHeader::GeneralDevice(header) => header, + _ => { + info!("Unsupported device"); + continue; + } + }; + + let virtio_did = if device.did() < 0x1040 { + // Transitional device: use PCI subsystem id + header.subsystem_id() + } else { + device.did() - 0x1040 + }; + + let mut common_cfg = None; + let mut device_cfg = None; + let mut notify_cfg = None; + for capability in device.capabilities() { + match capability { + Capability::VendorSpecific(data, size) => { + if let Ok(Some(cap)) = pci::Cap::read(header.bars(), &data) { + info!("{:?}", cap); + match cap { + pci::Cap::CommonCfg(cfg) => common_cfg = Some(cfg), + pci::Cap::DeviceCfg(cfg) => device_cfg = Some(cfg), + pci::Cap::NotifyCfg(cfg) => notify_cfg = Some(cfg), + cap => (), + } + } else { + info!("Unsupported virtio cap {:#?}", &data); + } + }, + cap => info!("Capability = {:#?}", cap) + } + } + + match (common_cfg, device_cfg, notify_cfg) { + (Some(common_cfg), Some(device_cfg), Some(notif_cfg)) => + devices.push(VirtioDevice { + virtio_did, device, header, common_cfg, device_cfg: Some(device_cfg), + common_features: CommonFeatures::empty(), notif_cfg, queues: Vec::new(), + irq_event: syscalls::create_interrupt_event(19, 0).unwrap() + }), + _ => () + } + } + + for device in devices.iter_mut() { + device.acknowledge(); + } + + for device in devices { + match device.virtio_did { + 1 => { + info!("Creating device"); + let mut device = net::VirtioNet::new(device); + info!("Initializing"); + device.init().unwrap(); + + info!("Pinging"); + ping::ping(device); + }, + id => info!("Unsupported did {}", id) + } + } + + // event loop + /*let man = WaitableManager::new(); + let handler = Box::new(PortHandler::::new("virtio:\0").unwrap()); + man.add_waitable(handler as Box); + man.run();*/ +} + +capabilities!(CAPABILITIES = Capabilities { + svcs: [ + sunrise_libuser::syscalls::nr::SleepThread, + sunrise_libuser::syscalls::nr::ExitProcess, + sunrise_libuser::syscalls::nr::CloseHandle, + sunrise_libuser::syscalls::nr::WaitSynchronization, + sunrise_libuser::syscalls::nr::OutputDebugString, + sunrise_libuser::syscalls::nr::GetSystemTick, + + sunrise_libuser::syscalls::nr::SetHeapSize, + sunrise_libuser::syscalls::nr::QueryMemory, + sunrise_libuser::syscalls::nr::MapSharedMemory, + sunrise_libuser::syscalls::nr::UnmapSharedMemory, + sunrise_libuser::syscalls::nr::ConnectToNamedPort, + sunrise_libuser::syscalls::nr::CreateInterruptEvent, + sunrise_libuser::syscalls::nr::QueryPhysicalAddress, + sunrise_libuser::syscalls::nr::MapMmioRegion, + sunrise_libuser::syscalls::nr::SendSyncRequestWithUserBuffer, + sunrise_libuser::syscalls::nr::ReplyAndReceiveWithUserBuffer, + sunrise_libuser::syscalls::nr::AcceptSession, + sunrise_libuser::syscalls::nr::CreateSession, + ], + raw_caps: [ + sunrise_libuser::caps::ioport(PCI_CONFIG_ADDRESS + 0), sunrise_libuser::caps::ioport(PCI_CONFIG_ADDRESS + 1), sunrise_libuser::caps::ioport(PCI_CONFIG_ADDRESS + 2), sunrise_libuser::caps::ioport(PCI_CONFIG_ADDRESS + 3), + sunrise_libuser::caps::ioport(PCI_CONFIG_DATA + 0), sunrise_libuser::caps::ioport(PCI_CONFIG_DATA + 1), sunrise_libuser::caps::ioport(PCI_CONFIG_DATA + 2), sunrise_libuser::caps::ioport(PCI_CONFIG_DATA + 3), + sunrise_libuser::caps::irq_pair(19, 0x3FF) + ] +}); diff --git a/virtio/src/net.rs b/virtio/src/net.rs new file mode 100644 index 000000000..e9f77ef66 --- /dev/null +++ b/virtio/src/net.rs @@ -0,0 +1,393 @@ +//! Network Device + +use crate::VirtioDevice; +use byteorder::{LE, ByteOrder}; +use crate::pci::Config; +use crate::virtqueue::VirtQueue; +use crate::{DeviceStatus, CommonFeatures}; +use bitflags::bitflags; +use alloc::vec::Vec; +use smoltcp::phy::{Device, Checksum, DeviceCapabilities, ChecksumCapabilities, RxToken, TxToken}; +use smoltcp::time::Instant; +use core::fmt; +use sunrise_libuser::error::Error; +use bit_field::BitField; +use log::*; + +bitflags! { + /// Features supported by the Virtio-Net driver. + struct Features: u64 { + /// Device handles packets with partial checksum. This “checksum offload” + /// is a common feature on modern network cards. + const CSUM = 1 << 0; + /// Driver handles packets with partial checksum. + const GUEST_CSUM = 1 << 1; + /// Control channel offloads reconfiguration support. + const CTRL_GUEST_OFFLOADS = 1 << 2; + /// Device maximum MTU reporting is supported. If offered by the device, + /// device advises driver about the value of its maximum MTU. If + /// negotiated, the driver uses mtu as the maximum MTU value. + const MTU = 1 << 3; + + /// Device has given MAC address. + const MAC = 1 << 5; + + /// Driver can receive TSOv4. + const GUEST_TSO4 = 1 << 7; + /// Driver can receive TSOv6. + const GUEST_TSO6 = 1 << 8; + /// Driver can receive TSO with ECN. + const GUEST_ECN = 1 << 9; + /// Driver can receive UFO. + const GUEST_UFO = 1 << 10; + /// Device can receive TSOv4. + const HOST_TSO4 = 1 << 11; + /// Device can receive TSOv6. + const HOST_TSO6 = 1 << 12; + /// Device can receive TSO with ECN. + const HOST_ECN = 1 << 13; + /// Device can receive UFO. + const HOST_UFO = 1 << 14; + /// Driver can merge receive buffers. + const MRG_RXBUF = 1 << 15; + /// Configuration status field is available. + const STATUS = 1 << 16; + /// Control channel is available. + const CTRL_VQ = 1 << 17; + /// Control channel RX mode support. + const CTRL_RX = 1 << 18; + /// Control channel VLAN filtering. + const CTRL_VLAN = 1 << 19; + + /// Driver can send gratuitous packets. + const GUEST_ANNOUNCE = 1 << 21; + /// Device supports multiqueue with automatic receive steering. + const MQ = 1 << 22; + /// Set MAC address through control channel. + const CTRL_MAC_ADDR = 1 << 23; + + /// Device can process duplicated ACKs and report number of coalesced + /// segments and duplicated ACKs. + const RSC_EXT = 1 << 61; + /// Device may act as a standby for a primary device with the same MAC + /// address. + const STANDBY = 1 << 62; + } +} + +pub struct NetConfiguration { + config: Config +} + +impl fmt::Debug for NetConfiguration { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_struct("NetConfiguration") + .field("mac", &self.mac()) + .field("status", &self.status()) + .field("max_virtqueue_pairs", &self.max_virtqueue_pairs()) + .field("mtu", &self.mtu()) + .finish() + } +} + +impl NetConfiguration { + pub fn mac(&self) -> [u8; 6] { + let mac0 = self.config.read_u8(0); + let mac1 = self.config.read_u8(1); + let mac2 = self.config.read_u8(2); + let mac3 = self.config.read_u8(3); + let mac4 = self.config.read_u8(4); + let mac5 = self.config.read_u8(5); + [mac0, mac1, mac2, mac3, mac4, mac5] + } + + pub fn status(&self) -> u16 { + self.config.read_u16::(6) + } + + pub fn max_virtqueue_pairs(&self) -> u16 { + self.config.read_u16::(8) + } + + pub fn mtu(&self) -> u16 { + self.config.read_u16::(10) + } +} + +/// Ensure the feature bit requirements of section 5.1.3.1 are met. +fn ensure_requirements_met(bits: u64) -> bool { + let bits = Features::from_bits_truncate(bits); + if bits.intersects(Features::GUEST_TSO4 | Features::GUEST_TSO6 | Features::GUEST_UFO) && !bits.contains(Features::GUEST_CSUM) { + return false; + } + + if bits.intersects(Features::GUEST_ECN) && !bits.intersects(Features::GUEST_TSO4 | Features::GUEST_TSO6) { + return false + } + + if bits.intersects(Features::HOST_TSO4 | Features::HOST_TSO6 | Features::HOST_UFO) && !bits.contains(Features::CSUM) { + return false; + } + + if bits.intersects(Features::HOST_ECN | Features::RSC_EXT) && !bits.intersects(Features::HOST_TSO4 | Features::HOST_TSO6) { + return false + } + + if bits.intersects(Features::CTRL_RX | Features::CTRL_VLAN | Features::GUEST_ANNOUNCE | Features::MQ | Features::CTRL_MAC_ADDR) && !bits.contains(Features::CTRL_VQ) { + return false; + } + + return true; +} + +#[derive(Debug)] +pub struct VirtioNet { + // TODO: Tmp + pub device: VirtioDevice, + net_config: NetConfiguration, + common_features: Features, +} + +impl VirtioNet { + pub fn new(mut device: VirtioDevice) -> VirtioNet { + let net_config = NetConfiguration { + config: device.acquire_device_cfg() + }; + VirtioNet { + device, net_config, common_features: Features::empty() + } + } + + /// 5.1.5: Device Initialization + pub fn init(&mut self) -> Result<(), Error> { + // TODO: Is it OK to assume device is ack'd in VirtioNet::init? + // 3.1.1 Driver Requirements: Device Initialization + self.device.common_cfg.set_device_status(DeviceStatus::DRIVER); + + // Negociate features + + // Minimum features that we **should** negociate, as part of 5.1.4.2 + let wanted_features = Features::MAC | Features::MTU; + + // Additional features that would be nice to have + let wanted_features = Features::CSUM | wanted_features; + + let common_features = self.device.negociate_features(wanted_features.bits(), 0, ensure_requirements_met)?; + self.common_features = Features::from_bits_truncate(common_features); + + // 4.1.5.1: PCI-specific initialization + // TODO: MSI-X Vector Configuration. + // Virtqueue Configuration + + // 5.1.5: Device Initialization + // Identify and initialize the receive and transmission virtqueues. + // TODO: Support VIRTIO_NET_F_MQ + info!("Setup virtqueues"); + for virtqueue_idx in 0..2 { + self.device.setup_virtqueue(virtqueue_idx); + } + + if self.common_features.contains(Features::CTRL_VQ) { + // TODO: Setup ctrl_vq + } + + info!("Push a ton of buffers"); + // Fill the receive queues buffer: See 5.1.6.3 + for queue in self.receive_queues() { + for item in 0..queue.len() { + //info!("Pushing buffer for item {}/{}", item, queue.len()); + //if item == 63 { + // info!("{:#?}", queue); + //} + queue.push_buffer_w(Vec::with_capacity(65560)) + } + } + + // Even with VIRTIO_NET_F_MQ, only receiveq1, transmitq1 and controlq are + // used by default. The driver would send the VIRTIO_NET_CTRL_MQ_VQ_PAIRS_SET + // command specifying the number of the transmit and receive queues to use. + + // If the VIRTIO_NET_F_MAC feature bit is set, the configuration space mac + // entry indicates the “physical” address of the network card, otherwise + // the driver would typically generate a random local MAC address. + info!("Get mac address"); + let mac = if self.common_features.contains(Features::MAC) { + self.net_config.mac() + } else { + // TODO: Generate random mac + [0, 1, 2, 3, 4, 5] + }; + + info!("We're good to go!"); + self.device.common_cfg.set_device_status(DeviceStatus::DRIVER_OK); + + Ok(()) + } + + fn receive_queues(&mut self) -> impl Iterator { + let num_queues = if self.common_features.contains(Features::MQ) { + self.net_config.max_virtqueue_pairs() as usize + } else { + 1 + }; + self.device.queues.iter_mut().step_by(2).take(num_queues).filter_map(|v| v.as_mut()) + } + + fn transmit_queues(&mut self) -> impl Iterator { + let num_queues = if self.common_features.contains(Features::MQ) { + self.net_config.max_virtqueue_pairs() as usize + } else { + 1 + }; + self.device.queues.iter_mut().skip(1).step_by(2).take(num_queues).filter_map(|v| v.as_mut()) + } + + fn control_queue(&mut self) -> Option<&mut VirtQueue> { + if self.common_features.contains(Features::CTRL_VQ) { + let num_queues = if self.common_features.contains(Features::MQ) { + self.net_config.max_virtqueue_pairs() as usize + } else { + 1 + }; + self.device.queues[num_queues * 2].as_mut() + } else { + None + } + } + + pub fn link_status(&self) -> bool { + if self.common_features.contains(Features::STATUS) { + self.net_config.status().get_bit(0) + } else { + true + } + } + + pub fn mac(&self) -> [u8; 6] { + if self.common_features.contains(Features::MAC) { + self.net_config.mac() + } else { + // TODO: Generate random mac + [0, 1, 2, 3, 4, 5] + } + } +} + +impl<'a> Device<'a> for VirtioNet { + type RxToken = VirtioNetRxToken; + type TxToken = VirtioNetTxToken<'a>; + + + fn receive(&'a mut self) -> Option<(Self::RxToken, Self::TxToken)> { + let buf = self.receive_queues().nth(0).unwrap().pop_buffer_w()?; + debug!("{:#?}", buf); + let rx = VirtioNetRxToken(buf); + let tx = VirtioNetTxToken(self); + Some((rx, tx)) + } + + fn transmit(&'a mut self) -> Option { + Some(VirtioNetTxToken(self)) + } + + fn capabilities(&self) -> DeviceCapabilities { + let mut device_caps = DeviceCapabilities::default(); + if self.common_features.contains(Features::MTU) { + device_caps.max_transmission_unit = self.net_config.mtu() as usize; + } else { + device_caps.max_transmission_unit = 65535; + } + if self.common_features.contains(Features::CSUM) { + device_caps.checksum.udp = Checksum::None; + device_caps.checksum.tcp = Checksum::None; + } + device_caps + } +} + +pub struct VirtioNetRxToken(Vec); + +impl RxToken for VirtioNetRxToken { + fn consume(self, timestamp: Instant, f: F) -> smoltcp::Result + where + F: FnOnce(&[u8]) -> smoltcp::Result + { + debug!("Consuming the buffer"); + f(&self.0[core::mem::size_of::()..]) + } +} + +pub struct VirtioNetTxToken<'a>(&'a mut VirtioNet); + +impl<'a> TxToken for VirtioNetTxToken<'a> { + fn consume(self, timestamp: Instant, len: usize, f: F) -> smoltcp::Result + where + F: FnOnce(&mut [u8]) -> smoltcp::Result + { + // TODO: Instead of allocating a new vec, use scatter-gather IO. + + let mut v = Vec::new(); + v.resize(len + core::mem::size_of::(), 0); + let res = f(&mut v[core::mem::size_of::()..]); + + v[0] = 0;/*NetHdrFlags::NEEDS_CSUM.bits()*/; + v[1] = 0; + LE::write_u16(&mut v[2..], 0); + LE::write_u16(&mut v[4..], 0); + LE::write_u16(&mut v[6..], 0); + LE::write_u16(&mut v[8..], 0); + LE::write_u16(&mut v[10..], 0); + + self.0.transmit_queues().nth(0).unwrap().push_buffer_r(v); + self.0.device.notify(1); + res + } +} + +bitflags! { + struct NetHdrFlags: u8 { + const NEEDS_CSUM = 1 << 0; + const DATA_VALID = 1 << 1; + const RSC_INFO = 1 << 2; + } +} + +bitflags! { + struct GsoType: u8 { + const None = 0; + const TCPv4 = 1; + const UDP = 3; + const TCPv6 = 4; + + const ECN = 0x80; + } +} + +#[repr(C)] +struct NetHdr { + flags: NetHdrFlags, + gso_type: GsoType, + hdr_len: u16, + gso_size: u16, + csum_start: u16, + csum_offset: u16, + num_buffers: u16, +} + +impl NetHdr { + fn new_transmission(needs_csum: bool, csum_start: u16, csum_offset: u16) -> NetHdr { + let mut flags = NetHdrFlags::empty(); + if needs_csum { + flags |= NetHdrFlags::NEEDS_CSUM; + } + NetHdr { + flags: flags, + gso_type: GsoType::None, + hdr_len: 0, + gso_size: 0, + csum_start: csum_start.to_le(), + csum_offset: csum_offset.to_le(), + num_buffers: 0 + } + } +} diff --git a/virtio/src/pci.rs b/virtio/src/pci.rs new file mode 100644 index 000000000..9d6108b9e --- /dev/null +++ b/virtio/src/pci.rs @@ -0,0 +1,345 @@ +use sunrise_libuser::pci::{BAR, MappedBAR}; +use byteorder::{LE, NativeEndian, ByteOrder}; +use bit_field::BitField; +use sunrise_libuser::error::KernelError; +use sunrise_libuser::pci::capabilities::RWCapability; +use crate::{DeviceStatus, Notification}; + +#[derive(Debug)] +pub enum Cap { + /// Common Configuration. + CommonCfg(CommonCfg), + /// Notifications. + NotifyCfg(NotificationCfg), + /// ISR Status. + IsrCfg(u8, u64, u64), + /// Device-specific configuration. + DeviceCfg(Config), + /// PCI configuration access. + PciCfg(u8, u64, u64), +} + +impl Cap { + pub fn read(bars: &[Option; 6], data: &RWCapability) -> Result, KernelError> { + let bar = data.read_u32(4).get_bits(0..8) as u8; + if bar > 5 { + return Ok(None) + } + + let offset = data.read_u32(8) as u64; + let length = data.read_u32(12) as u64; + + match data.read_u32(0).get_bits(24..32) { + 1 => Ok(Some(Cap::CommonCfg(CommonCfg { config: Config { + bar: bars[bar as usize].unwrap().map()?, + offset, length + }}))), + 2 => Ok(Some(Cap::NotifyCfg(NotificationCfg { config: Config { + bar: bars[bar as usize].unwrap().map()?, + offset, length + }, notify_off_multiplier: data.read_u32(16)}))), + 3 => Ok(Some(Cap::IsrCfg(bar, offset, length))), + 4 => Ok(Some(Cap::DeviceCfg(Config { + bar: bars[bar as usize].unwrap().map()?, + offset, length + }))), + 5 => Ok(Some(Cap::PciCfg(bar, offset, length))), + _ => Ok(None), + } + } +} + +const DEVICE_FEATURES_SELECT_OFF: u64 = 0; +const DEVICE_FEATURES_OFF: u64 = 4; +const DRIVER_FEATURES_SELECT_OFF: u64 = 8; +const DRIVER_FEATURES_OFF: u64 = 12; +const MSIX_CONFIG_OFF: u64 = 16; +const NUM_QUEUES_OFF: u64 = 18; +const DEVICE_STATUS_OFF: u64 = 20; +const CONFIG_GENERATION_OFF: u64 = 21; +const QUEUE_SELECT_OFF: u64 = 22; +const QUEUE_SIZE_OFF: u64 = 24; +const QUEUE_MSIX_VECTOR_OFF: u64 = 26; +const QUEUE_ENABLE_OFF: u64 = 28; +const QUEUE_NOTIFY_OFF_OFF: u64 = 30; +const QUEUE_DESC_OFF: u64 = 32; +const QUEUE_DRIVER_OFF: u64 = 40; +const QUEUE_DEVICE_OFF: u64 = 48; + +#[derive(Debug, Default)] +pub struct Queue { + pub size: u16, + pub msix_vector: u16, + pub enable: bool, + notify_off: u16, + pub desc: u64, + pub driver: u64, + pub device: u64, +} + +#[derive(Clone)] +pub struct QueueIter<'a>(&'a CommonCfg, u16); + +impl<'a> Iterator for QueueIter<'a> { + type Item = Queue; + fn next(&mut self) -> Option { + if self.1 < self.0.num_queues() { + let ret = Some(self.0.queue(self.1)); + self.1 += 1; + ret + } else { + None + } + } +} + +impl<'a> core::fmt::Debug for QueueIter<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + f.debug_list().entries(self.clone()).finish() + } +} + +#[derive(Debug)] +pub struct Config { + bar: MappedBAR, + offset: u64, + length: u64, +} + +impl Config { + pub fn read_u8(&self, additional_offset: u64) -> u8 { + assert!(additional_offset < self.length, "OOB Read: {} < {}", additional_offset, self.length); + self.bar.read_u8(self.offset + additional_offset) + } + + pub fn write_u8(&self, additional_offset: u64, data: u8) { + assert!(additional_offset < self.length, "OOB Write: {} < {}", additional_offset, self.length); + self.bar.write_u8(self.offset + additional_offset, data); + } + pub fn read_u16(&self, additional_offset: u64) -> u16 { + assert!(additional_offset.saturating_add(1) < self.length, "OOB Read: {} + 1 < {}", additional_offset, self.length); + self.bar.read_u16::(self.offset + additional_offset) + } + + pub fn write_u16(&self, additional_offset: u64, data: u16) { + assert!(additional_offset.saturating_add(1) < self.length, "OOB Write: {} + 1 < {}", additional_offset, self.length); + self.bar.write_u16::(self.offset + additional_offset, data); + } + + pub fn read_u32(&self, additional_offset: u64) -> u32 { + assert!(additional_offset.saturating_add(3) < self.length, "OOB Read: {} + 3 < {}", additional_offset, self.length); + self.bar.read_u32::(self.offset + additional_offset) + } + + pub fn write_u32(&self, additional_offset: u64, data: u32) { + assert!(additional_offset.saturating_add(3) < self.length, "OOB Write: {} + 3 < {}", additional_offset, self.length); + self.bar.write_u32::(self.offset + additional_offset, data); + } + + pub fn read_u64(&self, additional_offset: u64) -> u64 { + assert!(additional_offset.saturating_add(7) < self.length, "OOB Read: {} + 7 < {}", additional_offset, self.length); + let lo = self.bar.read_u32::(self.offset + additional_offset); + let hi = self.bar.read_u32::(self.offset + additional_offset + 4); + let mut bytes = [0u8; 8]; + NativeEndian::write_u32(&mut bytes, lo); + NativeEndian::write_u32(&mut bytes[4..], hi); + BO::read_u64(&bytes) + } + + pub fn write_u64(&self, additional_offset: u64, data: u64) { + assert!(additional_offset.saturating_add(7) < self.length, "OOB Write: {} + 7 < {}", additional_offset, self.length); + let mut bytes = [0; 8]; + BO::write_u64(&mut bytes, data); + let lo = NativeEndian::read_u32(&bytes); + let hi = NativeEndian::read_u32(&bytes[4..]); + self.bar.write_u32::(self.offset + additional_offset, lo); + self.bar.write_u32::(self.offset + additional_offset + 4, hi); + } +} + +pub struct CommonCfg { + config: Config, +} + +impl core::fmt::Debug for CommonCfg { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + f.debug_struct("CommonCfg") + .field("device_feature_bits", &self.device_feature_bits()) + .field("driver_feature_bits", &self.driver_feature_bits()) + .field("msix_config", &self.msix_config()) + .field("device_status", &self.device_status()) + .field("config_generation", &self.config_generation()) + .field("queues", &self.queues()) + .finish() + } +} + +impl CommonCfg { + pub fn device_feature_bits(&self) -> u64 { + self.config.write_u32::(DEVICE_FEATURES_SELECT_OFF, 0); + let datalo = self.config.read_u32::(DEVICE_FEATURES_OFF); + self.config.write_u32::(DEVICE_FEATURES_SELECT_OFF, 1); + let datahi = self.config.read_u32::(DEVICE_FEATURES_OFF); + + *0u64 + .set_bits(0..32, datalo as u64) + .set_bits(32..64, datahi as u64) + } + + pub fn driver_feature_bits(&self) -> u64 { + self.config.write_u32::(DRIVER_FEATURES_SELECT_OFF, 0); + let datalo = self.config.read_u32::(DRIVER_FEATURES_OFF); + self.config.write_u32::(DRIVER_FEATURES_SELECT_OFF, 1); + let datahi = self.config.read_u32::(DRIVER_FEATURES_OFF); + + *0u64 + .set_bits(0..32, datalo as u64) + .set_bits(32..64, datahi as u64) + } + + pub fn set_driver_features(&mut self, features: u64) { + self.config.write_u32::(DRIVER_FEATURES_SELECT_OFF, 0); + self.config.write_u32::(DRIVER_FEATURES_OFF, features.get_bits(0..32) as u32); + self.config.write_u32::(DRIVER_FEATURES_SELECT_OFF, 1); + self.config.write_u32::(DRIVER_FEATURES_OFF, features.get_bits(32..64) as u32); + } + + pub fn msix_config(&self) -> u16 { + self.config.read_u16::(MSIX_CONFIG_OFF) + } + + pub fn set_msix_config(&mut self, config: u16) { + self.config.write_u16::(MSIX_CONFIG_OFF, config); + } + + pub fn num_queues(&self) -> u16 { + self.config.read_u16::(NUM_QUEUES_OFF) + } + + pub fn device_status(&self) -> DeviceStatus { + // Maybe use from_bits_truncate? The device isn't supposed to change that field though, so + // this should never fail. On the other hand... do I want to trust the device? Maybe I should + // return None, and let the driver put the device in FAILED status. + DeviceStatus::from_bits(self.config.read_u8(DEVICE_STATUS_OFF)).unwrap() + } + + /// Sets the new device status bits. The old bits are or'd with the new ones (unless 0 is sent, + /// which is used to reset the device). + pub fn set_device_status(&mut self, newflags: DeviceStatus) { + if newflags.is_empty() { + self.config.write_u8(DEVICE_STATUS_OFF, 0); + } + let oldflags = self.device_status(); + self.config.write_u8(DEVICE_STATUS_OFF, (oldflags | newflags).bits()); + } + + pub fn config_generation(&self) -> u8 { + self.config.read_u8(CONFIG_GENERATION_OFF) + } + + fn queues(&self) -> impl Iterator + core::fmt::Debug + '_ { + QueueIter(self, 0) + } + + pub fn queue(&self, queue_idx: u16) -> Queue { + self.config.write_u16::(QUEUE_SELECT_OFF, queue_idx); + Queue { + size: self.config.read_u16::(QUEUE_SIZE_OFF), + msix_vector: self.config.read_u16::(QUEUE_MSIX_VECTOR_OFF), + enable: self.config.read_u16::(QUEUE_ENABLE_OFF) != 0, + notify_off: self.config.read_u16::(QUEUE_NOTIFY_OFF_OFF), + desc: self.config.read_u64::(QUEUE_DESC_OFF), + driver: self.config.read_u64::(QUEUE_DRIVER_OFF), + device: self.config.read_u64::(QUEUE_DEVICE_OFF) + } + } + + pub fn queue_size(&self, queue: u16) -> u16 { + self.config.write_u16::(QUEUE_SELECT_OFF, queue); + self.config.read_u16::(QUEUE_SIZE_OFF) + } + + pub fn set_queue_size(&self, queue: u16, size: u16) { + self.config.write_u16::(QUEUE_SELECT_OFF, queue); + self.config.write_u16::(QUEUE_SIZE_OFF, size); + } + + pub fn queue_msix_vector(&self, queue: u16) -> u16 { + self.config.write_u16::(QUEUE_SELECT_OFF, queue); + self.config.read_u16::(QUEUE_MSIX_VECTOR_OFF) + } + + pub fn set_queue_msix_vector(&mut self, queue: u16, vector: u16) { + self.config.write_u16::(QUEUE_SELECT_OFF, queue); + self.config.write_u16::(QUEUE_MSIX_VECTOR_OFF, vector); + } + + pub fn queue_enabled(&self, queue: u16) -> bool { + self.config.write_u16::(QUEUE_SELECT_OFF, queue); + self.config.read_u16::(QUEUE_ENABLE_OFF) != 0 + } + + pub fn set_queue_enabled(&mut self, queue: u16, enabled: bool) { + self.config.write_u16::(QUEUE_SELECT_OFF, queue); + self.config.write_u16::(QUEUE_ENABLE_OFF, enabled as u16); + } + + pub fn queue_notify_off(&self, queue: u16) -> u16 { + self.config.write_u16::(QUEUE_SELECT_OFF, queue); + self.config.read_u16::(QUEUE_NOTIFY_OFF_OFF) + } + + pub fn queue_desc(&self, queue: u16) -> u64 { + self.config.write_u16::(QUEUE_SELECT_OFF, queue); + self.config.read_u64::(QUEUE_DESC_OFF) + } + pub fn set_queue_desc(&mut self, queue: u16, off: u64) { + self.config.write_u16::(QUEUE_SELECT_OFF, queue); + self.config.write_u64::(QUEUE_DESC_OFF, off); + + } + pub fn queue_driver(&self, queue: u16) -> u64 { + self.config.write_u16::(QUEUE_SELECT_OFF, queue); + self.config.read_u64::(QUEUE_DRIVER_OFF) + } + pub fn set_queue_driver(&mut self, queue: u16, off: u64) { + self.config.write_u16::(QUEUE_SELECT_OFF, queue); + self.config.write_u64::(QUEUE_DRIVER_OFF, off); + + } + pub fn queue_device(&self, queue: u16) -> u64 { + self.config.write_u16::(QUEUE_SELECT_OFF, queue); + self.config.read_u64::(QUEUE_DEVICE_OFF) + } + pub fn set_queue_device(&mut self, queue: u16, off: u64) { + self.config.write_u16::(QUEUE_SELECT_OFF, queue); + self.config.write_u64::(QUEUE_DEVICE_OFF, off); + } + + pub fn set_queue(&mut self, queue_idx: u16, queue: &Queue) { + self.config.write_u16::(QUEUE_SELECT_OFF, queue_idx); + self.config.write_u16::(QUEUE_SIZE_OFF, queue.size); + self.config.write_u16::(QUEUE_MSIX_VECTOR_OFF, queue.msix_vector); + self.config.write_u64::(QUEUE_DESC_OFF, queue.desc); + self.config.write_u64::(QUEUE_DRIVER_OFF, queue.driver); + self.config.write_u64::(QUEUE_DEVICE_OFF, queue.device); + // Write enable last. + self.config.write_u16::(QUEUE_ENABLE_OFF, queue.enable as u16); + } +} + +#[derive(Debug)] +pub struct NotificationCfg { + config: Config, + notify_off_multiplier: u32 +} + +impl NotificationCfg { + pub fn notify_with_notification(&self, queue_notify_off: usize, notification: Notification) { + self.config.write_u32::(queue_notify_off as u64 * self.notify_off_multiplier as u64, notification.0.to_le()) + } + pub fn notify_with_virtqueue(&self, queue_notify_off: usize, virtqueue_idx: u16) { + log::info!("Notifying at {:#010x}, {:#010x?}", queue_notify_off as u64 * self.notify_off_multiplier as u64 + self.config.offset, self.config.bar); + self.config.write_u16::(queue_notify_off as u64 * self.notify_off_multiplier as u64, virtqueue_idx.to_le()) + } +} diff --git a/virtio/src/ping.rs b/virtio/src/ping.rs new file mode 100644 index 000000000..8ba83bfa8 --- /dev/null +++ b/virtio/src/ping.rs @@ -0,0 +1,110 @@ +use crate::net::VirtioNet; +use log::*; +use alloc::vec::Vec; +use sunrise_libuser::syscalls; +use sunrise_libuser::error::KernelError; +use sunrise_libuser::types::HandleRef; +use smoltcp::time::Duration; +use alloc::borrow::ToOwned; +use alloc::slice::SliceConcatExt; +use core::str; + +pub fn ping(device: VirtioNet) { + use smoltcp::time::{Duration, Instant}; + use smoltcp::phy::Device; + //use smoltcp::phy::wait as phy_wait; + use smoltcp::wire::{EthernetAddress, IpAddress, IpCidr, + Ipv4Address, TcpRepr, TcpPacket}; + use smoltcp::iface::{NeighborCache, EthernetInterfaceBuilder, Routes}; + use smoltcp::socket::{SocketSet, TcpSocket, TcpSocketBuffer}; + use byteorder::{NetworkEndian, ByteOrder}; + use alloc::collections::BTreeMap; + + let neighbor_cache = NeighborCache::new(BTreeMap::new()); + + let remote_addr = IpAddress::v4(10, 0, 2, 2); + + let tcp_rx_buffer = TcpSocketBuffer::new(vec![0; 64]); + let tcp_tx_buffer = TcpSocketBuffer::new(vec![0; 128]); + let tcp_socket = TcpSocket::new(tcp_rx_buffer, tcp_tx_buffer); + + let ethernet_addr = EthernetAddress(device.mac()); + let src_ipv4 = IpCidr::new(IpAddress::v4(10, 0, 2, 15), 24); + let ip_addrs = [src_ipv4]; + let default_v4_gw = Ipv4Address::new(10, 0, 2, 2); + let mut routes_storage = [None; 1]; + let mut routes = Routes::new(&mut routes_storage[..]); + routes.add_default_ipv4_route(default_v4_gw).unwrap(); + let mut iface = EthernetInterfaceBuilder::new(device) + .ethernet_addr(ethernet_addr) + .ip_addrs(ip_addrs) + .routes(routes) + .neighbor_cache(neighbor_cache) + .finalize(); + + let mut sockets = SocketSet::new(vec![]); + let tcp_handle = sockets.add(tcp_socket); + + { + let mut socket = sockets.get::(tcp_handle); + socket.connect((remote_addr, 10000), 49500).unwrap(); + } + let mut tcp_active = false; + + debug!("Starting loop:"); + loop { + let timestamp = Instant::from_millis(syscalls::get_system_tick().wrapping_mul(12) as i64 / 625_000_000); + match iface.poll(&mut sockets, timestamp) { + Ok(_) => {}, + Err(e) => { + debug!("poll error: {}", e); + } + } + + { + let mut socket = sockets.get::(tcp_handle); + if socket.is_active() && !tcp_active { + debug!("connected"); + } else if !socket.is_active() && tcp_active { + debug!("disconnected"); + break; + } + tcp_active = socket.is_active(); + + if socket.may_recv() { + + let data = socket.recv(|data| { + let mut data = data.to_owned(); + if data.len() > 0 { + debug!("recv data: {:?}", + str::from_utf8(data.as_ref()).unwrap_or("(invalid utf8)")); + data = data.split(|&b| b == b'\n').collect::>().concat(); + data.reverse(); + data.extend(b"\n"); + } + (data.len(), data) + }).unwrap(); + if socket.can_send() && data.len() > 0 { + debug!("send data: {:?}", + str::from_utf8(data.as_ref()).unwrap_or("(invalid utf8)")); + socket.send_slice(&data[..]).unwrap(); + } + } else if socket.may_send() { + socket.send_slice(b"Ohi!").unwrap(); + } + } + + wait(iface.device().device.irq_event.0.as_ref(), iface.poll_delay(&sockets, timestamp)); + } +} + +fn wait(handle: HandleRef<'_>, duration: Option) -> Result<(), ()> { + match syscalls::wait_synchronization(&[handle], duration.map(|v| v.millis() as usize * 1_000_000)) { + Ok(_) => { + debug!("Handle got signaled!"); + Ok(()) + }, + Err(KernelError::Timeout) => Ok(()), + Err(err) => Err(()) + } +} \ No newline at end of file diff --git a/virtio/src/virtqueue.rs b/virtio/src/virtqueue.rs new file mode 100644 index 000000000..776738df9 --- /dev/null +++ b/virtio/src/virtqueue.rs @@ -0,0 +1,408 @@ +use bitflags::bitflags; +use static_assertions::assert_eq_size; +use alloc::boxed::Box; +use alloc::vec::Vec; +use core::sync::atomic::{fence, Ordering}; +use core::fmt; +use sunrise_libuser::mem::virt_to_phys; +use log::info; + +#[repr(C)] +struct Ptr { + ptr: usize, + len: usize, +} + +pub struct VirtQueue { + free_head: usize, + last_used: u16, + /// An array of queue_size descriptors + descriptor_area: Box<[Descriptor]>, + /// An array matching descriptor_area, which contains the Virtual Address of + /// the associated descriptor. + virt_area: Box<[usize]>, + /// Contains an array of queue_size + driver_area: Box, + /// Contains an array of queue_size + device_area: Box, +} + +impl fmt::Debug for VirtQueue { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + + struct DriverRingIterator<'a>(&'a [u16], &'a [Descriptor], u16); + impl<'a> fmt::Debug for DriverRingIterator<'a> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_list().entries(self.0.iter().map(|v| { + (v, self.1.get(*v as usize)) + }).take(self.2 as usize)).finish() + } + } + + struct DeviceRingIterator<'a>(&'a [UsedElem], &'a [Descriptor], u16, u16); + impl<'a> fmt::Debug for DeviceRingIterator<'a> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_list().entries(self.0.iter().map(|v| { + (v.id, v.len, self.1.get(v.id as usize)) + }).skip(self.2 as usize).take(self.3 as usize)).finish() + } + } + + f.debug_struct("VirtQueue") + .field("free_head", &self.free_head) + .field("last_used", &self.last_used) + .field("driver_area", &self.driver_area) + .field("driver_area_ring", &DriverRingIterator(self.driver_area.ring(), &*self.descriptor_area, self.driver_area.idx())) + .field("device_area", &self.device_area) + .field("device_area_ring", &DeviceRingIterator(self.device_area.ring(), &*self.descriptor_area, self.last_used, self.device_area.idx())) + .finish() + } +} + +impl VirtQueue { + pub fn new(queue_size: u16) -> VirtQueue { + let mut descriptor_area = vec![Descriptor { + addr: 0, + len: 0, + flags: 0, + next: 0, + }; queue_size as usize].into_boxed_slice(); + + for (idx, item) in descriptor_area.iter_mut().enumerate() { + item.next = (idx as u16 + 1).to_le(); + } + + let driver_area = Avail::new(queue_size); + let device_area = Used::new(queue_size); + + VirtQueue { + free_head: 0, + last_used: 0, + virt_area: vec![0; queue_size as usize].into_boxed_slice(), + descriptor_area, + driver_area, + device_area + } + } + + pub fn len(&self) -> u16 { + self.descriptor_area.len() as u16 + } + + pub fn descriptor_area_dma_addr(&self) -> u64 { + let vaddr = &*self.descriptor_area as *const _ as *const u8; + let paddr = virt_to_phys(vaddr) as u64; + info!("Getting descriptor DMA ADDR for {:p} => {:#010x}", vaddr, paddr); + paddr + } + + pub fn device_area_dma_addr(&self) -> u64 { + let vaddr = &*self.device_area as *const _ as *const u8; + let paddr = virt_to_phys(vaddr) as u64; + info!("Getting device DMA ADDR for {:p} => {:#010x}", vaddr, paddr); + paddr + } + + pub fn driver_area_dma_addr(&self) -> u64 { + let vaddr = &*self.driver_area as *const _ as *const u8; + let paddr = virt_to_phys(vaddr) as u64; + info!("Getting driver DMA ADDR for {:p} => {:#010x}", vaddr, paddr); + paddr + } + + /// 2.6.13: Supplying Buffers to The Device + pub fn push_buffer_r(&mut self, mut buf: Vec) { + assert!(buf.len() != 0); + + // TODO: Blow up if cur_free_head == EOL marker. + let cur_free_head = self.free_head; + self.free_head = u16::from_le(self.descriptor_area[cur_free_head].next) as usize; + + self.descriptor_area[cur_free_head].addr = (virt_to_phys(buf.as_mut_ptr()) as u64).to_le(); + self.virt_area[cur_free_head] = buf.as_ptr() as usize; + self.descriptor_area[cur_free_head].len = (buf.len() as u32).to_le(); + self.descriptor_area[cur_free_head].flags = DescriptorFlags::empty().bits().to_le(); + self.descriptor_area[cur_free_head].next = 0; + core::mem::forget(buf); + + self.driver_area.push_buffer(cur_free_head as u16); + } + + /// 2.6.13: Supplying Buffers to The Device + pub fn push_buffer_w(&mut self, mut buf: Vec) { + assert!(buf.capacity() != 0); + + // TODO: Blow up if cur_free_head == EOL marker. + let cur_free_head = self.free_head; + self.free_head = u16::from_le(self.descriptor_area[cur_free_head].next) as usize; + + self.descriptor_area[cur_free_head].addr = (virt_to_phys(buf.as_mut_ptr()) as u64).to_le(); + self.virt_area[cur_free_head] = buf.as_ptr() as usize; + self.descriptor_area[cur_free_head].len = (buf.capacity() as u32).to_le(); + self.descriptor_area[cur_free_head].flags = DescriptorFlags::WRITE.bits().to_le(); + self.descriptor_area[cur_free_head].next = 0; + core::mem::forget(buf); + + self.driver_area.push_buffer(self.free_head as u16); + } + + pub fn pop_buffer_w(&mut self) -> Option> { + if self.last_used != self.device_area.idx() { + let ring_len = self.device_area.ring().len(); + let used_elem = self.device_area.ring()[self.last_used as usize % ring_len]; + let used_elem_id = used_elem.id as usize; + assert!(!DescriptorFlags::from_bits_truncate(self.descriptor_area[used_elem_id].flags).contains(DescriptorFlags::NEXT)); + let addr = self.virt_area[used_elem_id]; + let capacity = self.descriptor_area[used_elem_id].len as usize; + let len = used_elem.len as usize; + + let ret = unsafe { + Vec::from_raw_parts(addr as *mut u8, len, capacity) + }; + + self.last_used += 1; + + Some(ret) + } else { + None + } + } + + pub fn get_available_idx(&self) -> u16 { + self.driver_area.idx() + } + + pub fn device_notif_suppressed(&self) -> bool { + self.device_area.flags().contains(UsedFlags::NO_NOTIFY) + } + + /*pub fn push_buffer_ro(&mut self, buf: &[u8]) -> CompletionToken { + + }*/ +} + +bitflags! { + struct DescriptorFlags: u16 { + /// This marks a buffer as continuing via the next field. + const NEXT = 1; + /// This marks a buffer as write-only (otherwise read-only). + const WRITE = 2; + /// This means the buffer contains a list of buffer descriptors. + const INDIRECT = 4; + } +} + +#[repr(C, align(16))] +#[derive(Clone, Copy)] +pub struct Descriptor { + /// Address (guest-physical) + addr: u64, + /// Length + len: u32, + /// The flags as indicated above + flags: u16, + /// We chain unused descriptors via this, too + next: u16 +} + +impl fmt::Debug for Descriptor { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_struct("Descriptor") + .field("addr", &self.addr()) + .field("len", &self.len()) + .field("flags", &self.flags()) + .field("next", &self.next()) + .finish() + } +} + +impl Descriptor { + fn addr(&self) -> u64 { + u64::from_le(self.addr) + } + fn len(&self) -> u32 { + u32::from_le(self.len) + } + fn flags(&self) -> DescriptorFlags { + DescriptorFlags::from_bits_truncate(u16::from_le(self.flags)) + } + fn next(&self) -> u16 { + u16::from_le(self.next) + } +} + +assert_eq_size!(Descriptor, [u8; 16]); + +bitflags! { + struct AvailFlags: u16 { + /// The driver uses this to advise the device: don't kick me when you + /// add a buffer. It's unreliable, so it's simply an optimization. + const NO_INTERRUPT = 1; + } +} + +#[repr(C)] +pub struct Avail { + flags: u16, + idx: u16, + // Array of queue_size elements. The last element is reserved for used_event! + ring_and_used_event: [u16], +} + +impl fmt::Debug for Avail { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_struct("Avail") + .field("flags", &self.flags()) + .field("idx", &self.idx()) + //.field("ring", &self.ring()) + .field("used_event", &self.used_event()) + .finish() + } +} + +impl Avail { + fn new(queue_size: u16) -> Box { + let vec = vec![0u16; queue_size as usize + 3]; + let b = Box::leak(vec.into_boxed_slice()); + unsafe { + Box::from_raw(core::mem::transmute(Ptr { + ptr: b as *mut _ as *mut u8 as usize, + // Ignore flags and idx. Keep used_event in. + len: b.len() - 2, + })) + } + } + + fn flags(&self) -> AvailFlags { + AvailFlags::from_bits_truncate(u16::from_le(self.flags)) + } + + fn set_flags(&mut self, flags: AvailFlags) { + self.flags = flags.bits().to_le(); + } + + fn idx(&self) -> u16 { + u16::from_le(self.idx) + } + + fn set_idx(&mut self, idx: u16) { + fence(Ordering::SeqCst); + self.idx = idx.to_le(); + } + + fn ring(&self) -> &[u16] { + &self.ring_and_used_event[..self.ring_and_used_event.len() - 1] + } + + fn ring_mut(&mut self) -> &mut [u16] { + let len = self.ring_and_used_event.len(); + &mut self.ring_and_used_event[..len - 1] + } + + fn used_event(&self) -> u16 { + u16::from_le(*self.ring_and_used_event.last().unwrap()) + } + + fn push_buffer(&mut self, desc_idx: u16) { + let len = self.ring_mut().len(); + let idx = self.idx() as usize % len; + self.ring_mut()[idx] = desc_idx.to_le(); + self.set_idx(self.idx().wrapping_add(1)); + } +} + +bitflags! { + struct UsedFlags: u16 { + /// The device uses this to advise the driver: don't kick me when you + /// add a buffer. It's unreliable, so it's simply an optimization. + const NO_NOTIFY = 1; + } +} + +#[repr(C)] +#[derive(Clone, Copy)] +struct UsedElem { + /// Index of start of used descriptor chain. + id: u32, + /// Total length of the descriptor chain which was written to. + len: u32 +} + +impl UsedElem { + fn id(&self) -> u32 { + u32::from_le(self.id) + } + + fn len(&self) -> u32 { + u32::from_le(self.len) + } +} + +impl fmt::Debug for UsedElem { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_struct("Used") + .field("id", &self.id()) + .field("len", &self.len()) + .finish() + } +} + +union UsedElemOrAvailEvent { + elem: UsedElem, + avail_event: u16 +} + +#[repr(C)] +pub struct Used { + flags: u16, + idx: u16, + // Array of UsedElem. The last element is reserved for avail_event. + ring_and_avail_event: [UsedElemOrAvailEvent], +} + +impl fmt::Debug for Used { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_struct("Used") + .field("flags", &self.flags()) + .field("idx", &self.idx()) + //.field("ring", &self.ring()) // Wrong endianness + .field("avail_event", &self.avail_event()) + .finish() + } +} + +impl Used { + fn new(queue_size: u16) -> Box { + let vec = vec![0u32; queue_size as usize * 2 + 2]; + let b = Box::leak(vec.into_boxed_slice()); + unsafe { + Box::from_raw(core::mem::transmute(Ptr { + ptr: b as *mut _ as *mut u8 as usize, + // Ignore flags and idx. Keep used_event in. + len: (b.len() - 1) / 2, + })) + } + } + + fn flags(&self) -> UsedFlags { + UsedFlags::from_bits_truncate(u16::from_le(self.flags)) + } + + fn idx(&self) -> u16 { + u16::from_le(self.idx) + } + + fn ring(&self) -> &[UsedElem] { + unsafe { + // Safety: + let ring = &self.ring_and_avail_event[..self.ring_and_avail_event.len() - 1]; + core::mem::transmute(ring) + } + } + + fn avail_event(&self) -> u16 { + unsafe { + u16::from_le(self.ring_and_avail_event.last().unwrap().avail_event) + } + } +}