//! Stage 13: the virtual drivers (virtio-rng, virtio-console, virtio-gpu). //! Deterministic: QEMU provides every device (see test-args in Cargo.toml). //! - rng: the bytes are not constant and look random (bit balance), the //! getrandom path works for a user program //! - console: the bytes QEMU feeds in from tests/hvc0-input.txt arrive, //! and writes to the host complete without drops //! - gpu: GET_DISPLAY_INFO works, a bad resource id is refused with the //! right error, the framebuffer reads back what was written, the text //! console moved onto it, `tile` draws through /dev/fb0 //! - /dev lists the devices, and nothing leaks frames //! (scripts/gpu-check.sh checks the actual screen through QEMU.) #![no_std] #![no_main] #![feature(custom_test_frameworks)] #![test_runner(rustbelt::test_runner)] #![reexport_test_harness_main = "test_main"] extern crate alloc; use alloc::{string::String, vec::Vec}; use bootloader::{BootInfo, entry_point}; use core::panic::PanicInfo; use rustbelt::process::{self, ExitStatus, SpawnArgs}; use rustbelt::virtio::{console, gpu, rng}; use rustbelt::{fs, memory, serial_println, time, vga_buffer}; entry_point!(main); fn main(boot_info: &'static BootInfo) -> ! { rustbelt::init(); rustbelt::init_memory(boot_info); rustbelt::init_smp(); fs::init(); rustbelt::init_storage(); for line in rustbelt::init_drivers() { serial_println!("[virtio] {}", line); } test_main(); rustbelt::hlt_loop(); } fn run(name: &str, args: &[&str]) -> ExitStatus { let mut spawn = SpawnArgs::kernel(name); spawn.args.extend(args.iter().map(|a| String::from(*a))); let p = process::spawn_path(&alloc::format!("/mnt/disk0/bin/{}", name), spawn).expect("spawn failed"); process::wait(&p) } #[test_case] fn rng_bytes_are_random() { assert!(rng::available(), "no virtio-rng: test-args need -device virtio-rng-pci"); let mut a = [0u8; 4096]; let mut b = [0u8; 4096]; assert!(rng::fill(&mut a) && rng::fill(&mut b), "virtio-rng gave no bytes"); assert_ne!(a, b, "two reads gave the same bytes"); assert!(a.iter().any(|&x| x != a[0]), "constant bytes"); // 32768 random bits: the number of ones should be close to half // (the standard deviation is ~90, so +-1000 never fails by chance). let ones: u32 = a.iter().map(|x| x.count_ones()).sum(); assert!((15384..=17384).contains(&ones), "{} one bits out of 32768", ones); // All 256 byte values should show up in 8 KiB (each ~32 times). let mut seen = [false; 256]; a.iter().chain(b.iter()).for_each(|&x| seen[x as usize] = true); assert!(seen.iter().all(|&s| s), "some byte values never appeared"); assert_ne!(rng::u64(), rng::u64()); } #[test_case] fn random_program_uses_getrandom() { assert_eq!(run("random", &["-n", "32"]), ExitStatus::Exited(0)); assert_eq!(run("random", &["-d", "6", "10"]), ExitStatus::Exited(0)); assert_eq!(run("random", &["-n", "x"]), ExitStatus::Exited(2)); // usage error } #[test_case] fn console_receives_host_input() { assert!(console::present(), "no virtio-console: test-args need virtio-serial-pci + virtconsole"); // QEMU sends the input file as soon as the port is ready; wait up to 5 s. let expected = b"hello from the host over hvc0\n"; let mut got: Vec = Vec::new(); let deadline = time::now_us() + 5_000_000; while got.len() < expected.len() && time::now_us() < deadline { got.extend(console::drain_input()); rustbelt::thread::yield_now(); } assert_eq!(&got[..], &expected[..], "hvc0 input: {:?}", core::str::from_utf8(&got)); } #[test_case] fn console_writes_complete() { let dropped = console::DROPPED.load(core::sync::atomic::Ordering::Relaxed); // 8 KiB: more than all 8 transmit slots at once, so slots get reused. let line = b"Rustbelt hvc0 output test: 0123456789 abcdefghijklmnopqrstuvwxyz\n"; for _ in 0..128 { assert_eq!(console::write(line), line.len()); } assert_eq!(console::DROPPED.load(core::sync::atomic::Ordering::Relaxed), dropped); } #[test_case] fn gpu_display_info_and_errors() { let fb = gpu::framebuffer().expect("no virtio-gpu: test-args need -device virtio-vga"); assert_eq!((fb.width, fb.height), (gpu::WIDTH, gpu::HEIGHT)); assert_eq!(fb.size_bytes(), 1024 * 768 * 4); assert_eq!(gpu::command(gpu::CMD_GET_DISPLAY_INFO, &[]), gpu::RESP_OK_DISPLAY_INFO); let (w, h) = gpu::preferred_size(); assert!(w >= 640 && h >= 480, "display {}x{}", w, h); // Flushing a resource that doesn't exist must fail, not hang or crash. let bad_flush = [0, 0, 16, 16, 999, 0]; assert_eq!(gpu::command(gpu::CMD_RESOURCE_FLUSH, &bad_flush), gpu::RESP_ERR_INVALID_RESOURCE_ID); assert!(gpu::flush(0, 0, 1024, 768), "a full-screen flush failed"); assert!(gpu::flush(1000, 700, 500, 500), "a clipped flush failed"); } #[test_case] fn framebuffer_reads_back() { let fb = gpu::framebuffer().unwrap(); let px = |x: u32, y: u32| unsafe { fb.pixels().add((y * fb.stride + x) as usize) }; // Write a pattern into a corner, flush it, read it back. for y in 700..768 { for x in 900..1024 { unsafe { px(x, y).write_volatile(x * 7 ^ y * 13) }; } } assert!(gpu::flush(900, 700, 124, 68)); for y in 700..768 { for x in 900..1024 { assert_eq!(unsafe { px(x, y).read_volatile() }, x * 7 ^ y * 13); } } } #[test_case] fn text_console_is_on_the_framebuffer() { assert!(vga_buffer::on_framebuffer()); assert_eq!(vga_buffer::size(), (128, 48)); // Print a line: some pixels in the bottom text row must be lit. rustbelt::println!("framebuffer console test"); let fb = gpu::framebuffer().unwrap(); let row_y = 768 - 2 * 16; // the line above the (empty) cursor line let lit = (row_y..row_y + 16) .flat_map(|y| (0..24 * 8).map(move |x| (x, y))) .filter(|&(x, y)| unsafe { fb.pixels().add((y * fb.stride + x) as usize).read_volatile() } != 0) .count(); assert!(lit > 200, "only {} lit pixels where the text is", lit); } #[test_case] fn dev_lists_the_devices() { let dev = fs::lookup("/dev").expect("no /dev"); let mut names: Vec = dev.list().expect("list /dev").into_iter().map(|e| e.name).collect(); names.sort(); assert_eq!(names, ["fb0", "hvc0", "null", "random", "urandom"]); assert_eq!(fs::lookup("/dev/fb0").unwrap().size(), 1024 * 768 * 4); } #[test_case] fn tile_draws_through_dev_fb0() { let frames = memory::frames_in_use(); assert_eq!(run("tile", &[]), ExitStatus::Exited(0)); let fb = gpu::framebuffer().unwrap(); let px = |x: u32, y: u32| unsafe { fb.pixels().add((y * fb.stride + x) as usize).read_volatile() }; assert_eq!(px(11, 300), 0x33ccff, "focused border"); assert_eq!(px(525, 18), 0x89b4fa, "window 1"); assert_eq!(px(778, 397), 0xf9e2af, "window 3"); assert_eq!(px(2, 2), 0x1e1e2e, "wallpaper"); assert_eq!(vga_buffer::size(), (61, 46), "console moved into window 0"); assert_eq!(run("tile", &["-r"]), ExitStatus::Exited(0)); assert_eq!(vga_buffer::size(), (128, 48)); assert_eq!(memory::frames_in_use(), frames, "frames leaked"); } #[panic_handler] fn panic(info: &PanicInfo) -> ! { rustbelt::test_panic_handler(info) }