diff --git a/Cargo.lock b/Cargo.lock index 2dca705708ae71edce6075734870a5f5b0720fa7..ba7d5f672df7663c0b62007a3410cd3ac198f3ac 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,12 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "bit_field" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcb6dd1c2376d2e096796e234a70e17e94cc2d5d54ff8ce42b28cef1d0d359a4" + [[package]] name = "bitflags" version = "1.3.2" @@ -47,15 +53,33 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "orbclient" +version = "0.3.21" +source = "git+https://gitlab.redox-os.org/redox-os/orbclient.git?branch=no_std#0cf93f23b88fdeff6561a4f9b4b91798c3c3a4a1" + +[[package]] +name = "raw-cpuid" +version = "10.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "929f54e29691d4e6a9cc558479de70db7aa3d98cd6fe7ab86d7507aa2886b9d2" +dependencies = [ + "bitflags", +] + [[package]] name = "redox_bootloader" version = "0.1.0" dependencies = [ "linked_list_allocator", "log", + "orbclient", "redox_syscall", + "redox_uefi", + "redox_uefi_std", "redoxfs", "spin", + "x86", ] [[package]] @@ -67,6 +91,31 @@ dependencies = [ "bitflags", ] +[[package]] +name = "redox_uefi" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c63a3180c5aba47178029b21c1615fbdf87d2bf682669708ea15e9c71eb8935" + +[[package]] +name = "redox_uefi_alloc" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "524f31a58708b6d0ba2d4e734c1dcf14d287307b09118ff07ef25dd504773c7c" +dependencies = [ + "redox_uefi", +] + +[[package]] +name = "redox_uefi_std" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a520781d9bb97c9802dd196e7a550fafaeea62d94b2351422c3424981d3a2d" +dependencies = [ + "redox_uefi", + "redox_uefi_alloc", +] + [[package]] name = "redoxfs" version = "0.4.4" @@ -107,3 +156,14 @@ name = "uuid" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bcc7e3b898aa6f6c08e5295b6c89258d1331e9ac578cc992fb818759951bdc22" + +[[package]] +name = "x86" +version = "0.43.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e85eb056bbe47f56d75dc0ccc5fe9c12211ed141292f4d7485d2a7c3dedda09" +dependencies = [ + "bit_field", + "bitflags", + "raw-cpuid", +] diff --git a/Cargo.toml b/Cargo.toml index ed3b21a852d3a56e1d342c5b6653f1f6601ebe82..74fbb558ac54076a28eeea558ee9316c7818c734 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,9 +3,15 @@ name = "redox_bootloader" version = "0.1.0" edition = "2018" +# UEFI uses bin target +[[bin]] +name = "bootloader" +path = "src/main.rs" + +# BIOS uses lib target [lib] name = "bootloader" -path = "src/lib.rs" +path = "src/main.rs" crate-type = ["staticlib"] [dependencies] @@ -14,3 +20,15 @@ log = "0.4.14" redox_syscall = "0.2.10" redoxfs = { version = "0.4.4", default-features = false } spin = "0.9.2" + +[target.'cfg(target_os = "uefi")'.dependencies] +redox_uefi = "0.1.2" +redox_uefi_std = "0.1.5" + +[target.'cfg(target_os = "uefi")'.dependencies.orbclient] +git = "https://gitlab.redox-os.org/redox-os/orbclient.git" +branch = "no_std" +features = ["no_std"] + +[target."x86_64-unknown-uefi".dependencies] +x86 = "0.43.0" diff --git a/LICENSE b/LICENSE index 5deeece4f437a9f6f52cc1a344221225df6d2a27..ceeda84edb849335466a5133e78c7036865e4da6 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2017 Redox OS +Copyright (c) 2017-2022 Redox OS Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/Makefile b/Makefile index ff07d526ec0df1e337f64d9823eb40457d82c89b..48724f838d7120d69eb4c20ab126da39f96ccff0 100644 --- a/Makefile +++ b/Makefile @@ -1,40 +1,12 @@ -TARGET?=x86-unknown-none +TARGET?=x86_64-unknown-uefi BUILD=build/$(TARGET) export RUST_TARGET_PATH=$(CURDIR)/targets -ifeq ($(TARGET),x86-unknown-none) - -export LD=ld -m elf_i386 -export OBJCOPY=objcopy -export PARTED=parted -export QEMU=qemu-system-x86_64 - -all: $(BUILD)/bootloader.bin - -else - -all: - $(error target $(TARGET) not supported by bootloader yet) - -endif +include mk/$(TARGET).mk clean: rm -rf build -$(BUILD)/libbootloader.a: Cargo.lock Cargo.toml $(shell find src -type f) - mkdir -p $(BUILD) - cargo rustc --lib --target $(TARGET) --release -- -C soft-float -C debuginfo=2 --emit link=$@ - -$(BUILD)/bootloader.elf: linkers/$(TARGET).ld $(BUILD)/libbootloader.a - mkdir -p $(BUILD) - $(LD) --gc-sections -z max-page-size=0x1000 -T $< -o $@ $(BUILD)/libbootloader.a && \ - $(OBJCOPY) --only-keep-debug $@ $@.sym && \ - $(OBJCOPY) --strip-debug $@ - -$(BUILD)/bootloader.bin: $(BUILD)/bootloader.elf $(shell find asm/$(TARGET) -type f) - mkdir -p $(BUILD) - nasm -f bin -o $@ -l $@.lst -D STAGE3=$< -iasm/$(TARGET) asm/$(TARGET)/bootloader.asm - $(BUILD)/filesystem: mkdir -p $(BUILD) rm -f $@.partial @@ -42,36 +14,9 @@ $(BUILD)/filesystem: fallocate -l 1MiB $@.partial/kernel mv $@.partial $@ - $(BUILD)/filesystem.bin: $(BUILD)/filesystem mkdir -p $(BUILD) rm -f $@.partial fallocate -l 255MiB $@.partial redoxfs-ar $@.partial $< mv $@.partial $@ - -$(BUILD)/harddrive.bin: $(BUILD)/bootloader.bin $(BUILD)/filesystem.bin - mkdir -p $(BUILD) - rm -f $@.partial - fallocate -l 256MiB $@.partial - $(PARTED) -s -a minimal $@.partial mklabel msdos - $(PARTED) -s -a minimal $@.partial mkpart primary 1MiB 100% - dd if=$< of=$@.partial bs=1 count=446 conv=notrunc - dd if=$< of=$@.partial bs=512 skip=1 seek=1 conv=notrunc - dd if=$(BUILD)/filesystem.bin of=$@.partial bs=1MiB seek=1 conv=notrunc - mv $@.partial $@ - -qemu: $(BUILD)/harddrive.bin - $(QEMU) \ - -d cpu_reset \ - -d guest_errors \ - -no-reboot \ - -smp 4 -m 2048 \ - -chardev stdio,id=debug,signal=off,mux=on \ - -serial chardev:debug \ - -mon chardev=debug \ - -machine q35 \ - -net none \ - -enable-kvm \ - -cpu host \ - -drive file=$<,format=raw diff --git a/mk/x86-unknown-none.mk b/mk/x86-unknown-none.mk new file mode 100644 index 0000000000000000000000000000000000000000..502b633900a99fa3411a2e5865d970cd16d4b61c --- /dev/null +++ b/mk/x86-unknown-none.mk @@ -0,0 +1,46 @@ +export LD?=ld +export OBJCOPY?=objcopy +export PARTED?=parted +export QEMU?=qemu-system-x86_64 + +all: $(BUILD)/bootloader.bin + +$(BUILD)/libbootloader.a: Cargo.lock Cargo.toml $(shell find src -type f) + mkdir -p $(BUILD) + cargo rustc --lib --target $(TARGET) --release -- -C soft-float -C debuginfo=2 --emit link=$@ + +$(BUILD)/bootloader.elf: linkers/$(TARGET).ld $(BUILD)/libbootloader.a + mkdir -p $(BUILD) + $(LD) -m elf_i386 --gc-sections -z max-page-size=0x1000 -T $< -o $@ $(BUILD)/libbootloader.a && \ + $(OBJCOPY) --only-keep-debug $@ $@.sym && \ + $(OBJCOPY) --strip-debug $@ + +$(BUILD)/bootloader.bin: $(BUILD)/bootloader.elf $(shell find asm/$(TARGET) -type f) + mkdir -p $(BUILD) + nasm -f bin -o $@ -l $@.lst -D STAGE3=$< -iasm/$(TARGET) asm/$(TARGET)/bootloader.asm + +$(BUILD)/harddrive.bin: $(BUILD)/bootloader.bin $(BUILD)/filesystem.bin + mkdir -p $(BUILD) + rm -f $@.partial + fallocate -l 256MiB $@.partial + $(PARTED) -s -a minimal $@.partial mklabel msdos + $(PARTED) -s -a minimal $@.partial mkpart primary 1MiB 100% + dd if=$< of=$@.partial bs=1 count=446 conv=notrunc + dd if=$< of=$@.partial bs=512 skip=1 seek=1 conv=notrunc + dd if=$(BUILD)/filesystem.bin of=$@.partial bs=1MiB seek=1 conv=notrunc + mv $@.partial $@ + +qemu: $(BUILD)/harddrive.bin + $(QEMU) \ + -d cpu_reset \ + -d guest_errors \ + -no-reboot \ + -smp 4 -m 2048 \ + -chardev stdio,id=debug,signal=off,mux=on \ + -serial chardev:debug \ + -mon chardev=debug \ + -machine q35 \ + -net none \ + -enable-kvm \ + -cpu host \ + -drive file=$<,format=raw diff --git a/mk/x86_64-unknown-uefi.mk b/mk/x86_64-unknown-uefi.mk new file mode 100644 index 0000000000000000000000000000000000000000..9c0fa31851aa634d75b88113f3c86518995b4e03 --- /dev/null +++ b/mk/x86_64-unknown-uefi.mk @@ -0,0 +1,59 @@ +export LD?=ld +export OBJCOPY?=objcopy +export PARTED?=parted +export QEMU?=qemu-system-x86_64 + +all: $(BUILD)/bootloader.efi + +$(BUILD)/bootloader.efi: Cargo.lock Cargo.toml $(shell find src -type f) + mkdir -p $(BUILD) + cargo rustc \ + -Z build-std=core,alloc \ + -Z build-std-features=compiler-builtins-mem \ + --target $(TARGET) \ + --bin bootloader \ + --release \ + -- \ + -C soft-float \ + --emit link=$@ + +$(BUILD)/esp.bin: $(BUILD)/bootloader.efi + mkdir -p $(BUILD) + rm -f $@.partial + fallocate -l 64MiB $@.partial + mkfs.vfat -F 32 $@.partial + mmd -i $@.partial efi + mmd -i $@.partial efi/boot + mcopy -i $@.partial $< ::efi/boot/bootx64.efi + mv $@.partial $@ + +$(BUILD)/harddrive.bin: $(BUILD)/esp.bin $(BUILD)/filesystem.bin + mkdir -p $(BUILD) + rm -f $@.partial + fallocate -l 320MiB $@.partial + $(PARTED) -s -a minimal $@.partial mklabel gpt + $(PARTED) -s -a minimal $@.partial mkpart ESP FAT32 1MiB 65MiB + $(PARTED) -s -a minimal $@.partial mkpart REDOXFS 65MiB 100% + $(PARTED) -s -a minimal $@.partial toggle 1 boot + dd if=$(BUILD)/esp.bin of=$@.partial bs=1MiB seek=1 conv=notrunc + dd if=$(BUILD)/filesystem.bin of=$@.partial bs=1MiB seek=65 conv=notrunc + mv $@.partial $@ + +$(BUILD)/firmware.rom: + cp /usr/share/OVMF/OVMF_CODE.fd $@ + +qemu: $(BUILD)/harddrive.bin $(BUILD)/firmware.rom + $(QEMU) \ + -d cpu_reset \ + -d guest_errors \ + -no-reboot \ + -smp 4 -m 2048 \ + -chardev stdio,id=debug,signal=off,mux=on \ + -serial chardev:debug \ + -mon chardev=debug \ + -machine q35 \ + -net none \ + -enable-kvm \ + -cpu host \ + -bios $(BUILD)/firmware.rom \ + -drive file=$<,format=raw diff --git a/src/arch/x86_64/mod.rs b/src/arch/x86_64/mod.rs index 67c3b1cfc57b52b360e83f83603f9bcc11b16487..d05990160530bc4b5260a79bf7c68ddb536af36b 100644 --- a/src/arch/x86_64/mod.rs +++ b/src/arch/x86_64/mod.rs @@ -1,4 +1,3 @@ pub use self::paging::paging_create; mod paging; -mod panic; diff --git a/src/lib.rs b/src/lib.rs deleted file mode 100644 index a714479e5a9d40bf96e5945e79ae055cefa235f9..0000000000000000000000000000000000000000 --- a/src/lib.rs +++ /dev/null @@ -1,13 +0,0 @@ -#![no_std] -#![feature(asm)] -#![feature(lang_items)] -#![feature(llvm_asm)] - -#[macro_use] -extern crate alloc; - -#[macro_use] -mod os; - -mod arch; -mod logger; diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000000000000000000000000000000000000..391c49260f301714d6453bf96b3fc3f33c78bbe4 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,23 @@ +#![no_std] +#![feature(asm)] +#![feature(lang_items)] +#![feature(llvm_asm)] +#![cfg_attr( + target_os = "uefi", + no_main, + feature(control_flow_enum), + feature(try_trait_v2), +)] + +#[cfg_attr(target_os = "none", macro_use)] +extern crate alloc; + +#[cfg(target_os = "uefi")] +#[macro_use] +extern crate uefi_std as std; + +#[macro_use] +mod os; + +mod arch; +mod logger; diff --git a/src/os/bios/mod.rs b/src/os/bios/mod.rs index bd5a5d7e0f38d860e4caa440089a88384eda9741..f6ddcf2b55c80669911b8761a50914458c20566b 100644 --- a/src/os/bios/mod.rs +++ b/src/os/bios/mod.rs @@ -27,6 +27,7 @@ mod macros; mod disk; mod memory_map; +mod panic; mod thunk; mod vbe; mod vga; @@ -331,6 +332,11 @@ pub unsafe extern "C" fn kstart( } println!("\rKernel: {}/{} MiB", i / 1024 / 1024, size / 1024 / 1024); + let magic = &kernel[..4]; + if magic != b"\x7FELF" { + panic!("Kernel has invalid magic number {:#X?}", magic); + } + kernel }; diff --git a/src/arch/x86_64/panic.rs b/src/os/bios/panic.rs similarity index 100% rename from src/arch/x86_64/panic.rs rename to src/os/bios/panic.rs diff --git a/src/os/uefi/arch/aarch64/memory_map.rs b/src/os/uefi/arch/aarch64/memory_map.rs new file mode 100644 index 0000000000000000000000000000000000000000..a5607a001b906f765313eba7ed232910291d5cf2 --- /dev/null +++ b/src/os/uefi/arch/aarch64/memory_map.rs @@ -0,0 +1,20 @@ +use uefi::memory::MemoryDescriptor; + +pub unsafe fn memory_map() -> usize { + let uefi = std::system_table(); + + let mut map: [u8; 65536] = [0; 65536]; + let mut map_size = map.len(); + let mut map_key = 0; + let mut descriptor_size = 0; + let mut descriptor_version = 0; + let _ = (uefi.BootServices.GetMemoryMap)( + &mut map_size, + map.as_mut_ptr() as *mut MemoryDescriptor, + &mut map_key, + &mut descriptor_size, + &mut descriptor_version + ); + + map_key +} diff --git a/src/os/uefi/arch/aarch64/mod.rs b/src/os/uefi/arch/aarch64/mod.rs new file mode 100644 index 0000000000000000000000000000000000000000..5c1818a6fa08727b61230fbbf65e7413ca813e83 --- /dev/null +++ b/src/os/uefi/arch/aarch64/mod.rs @@ -0,0 +1,269 @@ +use core::{mem, ptr}; +use orbclient::{Color, Renderer}; +use std::fs::find; +use std::proto::Protocol; +use uefi::guid::Guid; +use uefi::status::{Error, Result}; + +use crate::display::{Display, ScaledDisplay, Output}; +use crate::image::{self, Image}; +use crate::key::{key, Key}; +use crate::redoxfs; +use crate::text::TextDisplay; + +use self::memory_map::memory_map; +use self::paging::paging; + +mod memory_map; +mod paging; +mod partitions; + +static KERNEL: &'static str = concat!("\\", env!("BASEDIR"), "\\kernel"); +static SPLASHBMP: &'static [u8] = include_bytes!("../../../res/splash.bmp"); + +static KERNEL_OFFSET: u64 = 0xFFFF_FF00_0000_0000; + +static KERNEL_PHYSICAL: u64 = 0x4000_0000; +static mut KERNEL_SIZE: u64 = 0; +static mut KERNEL_ENTRY: u64 = 0; + +static mut DTB_PHYSICAL: u64 = 0; + +#[no_mangle] +pub extern "C" fn __chkstk() { + //TODO +} + +unsafe fn exit_boot_services(key: usize) { + let handle = std::handle(); + let uefi = std::system_table(); + + let _ = (uefi.BootServices.ExitBootServices)(handle, key); +} + +unsafe fn enter() -> ! { + let entry_fn: extern "C" fn(dtb: u64) -> ! = mem::transmute(( + KERNEL_PHYSICAL + KERNEL_ENTRY - KERNEL_OFFSET + )); + entry_fn(DTB_PHYSICAL); +} + +fn get_correct_block_io() -> Result<redoxfs::Disk> { + // Get all BlockIo handles. + let mut handles = vec! [uefi::Handle(0); 128]; + let mut size = handles.len() * mem::size_of::<uefi::Handle>(); + + (std::system_table().BootServices.LocateHandle)(uefi::boot::LocateSearchType::ByProtocol, &uefi::guid::BLOCK_IO_GUID, 0, &mut size, handles.as_mut_ptr())?; + + let max_size = size / mem::size_of::<uefi::Handle>(); + let actual_size = std::cmp::min(handles.len(), max_size); + + // Return the handle that seems bootable. + for handle in handles.into_iter().take(actual_size) { + let block_io = redoxfs::Disk::handle_protocol(handle)?; + if !block_io.0.Media.LogicalPartition { + continue; + } + + let part = partitions::PartitionProto::handle_protocol(handle)?.0; + if part.sys == 1 { + continue; + } + assert_eq!({part.rev}, partitions::PARTITION_INFO_PROTOCOL_REVISION); + if part.ty == partitions::PartitionProtoDataTy::Gpt as u32 { + let gpt = unsafe { part.info.gpt }; + assert_ne!(gpt.part_ty_guid, partitions::ESP_GUID, "detected esp partition again"); + if gpt.part_ty_guid == partitions::REDOX_FS_GUID || gpt.part_ty_guid == partitions::LINUX_FS_GUID { + return Ok(block_io); + } + } else if part.ty == partitions::PartitionProtoDataTy::Mbr as u32 { + let mbr = unsafe { part.info.mbr }; + if mbr.ty == 0x83 { + return Ok(block_io); + } + } else { + continue; + } + } + panic!("Couldn't find handle for partition"); +} + +static DTB_GUID: Guid = Guid(0xb1b621d5, 0xf19c, 0x41a5, [0x83, 0x0b, 0xd9, 0x15, 0x2c, 0x69, 0xaa, 0xe0]); + +fn find_dtb() -> Result<()> { + let cfg_tables = std::system_table().config_tables(); + for cfg_table in cfg_tables.iter() { + if cfg_table.VendorGuid == DTB_GUID { + unsafe { + DTB_PHYSICAL = cfg_table.VendorTable as u64; + println!("DTB: {:X}", DTB_PHYSICAL); + } + return Ok(()); + } + } + println!("Failed to find DTB"); + Err(Error::NotFound) +} + +fn redoxfs() -> Result<redoxfs::FileSystem> { + // TODO: Scan multiple partitions for a kernel. + redoxfs::FileSystem::open(get_correct_block_io()?) +} + +const MB: usize = 1024 * 1024; + +fn inner() -> Result<()> { + find_dtb()?; + + { + println!("Loading Kernel..."); + let (kernel, mut env): (Vec<u8>, String) = { + let (_i, mut kernel_file) = find(KERNEL)?; + let info = kernel_file.info()?; + let len = info.FileSize; + let mut kernel = Vec::with_capacity(len as usize); + let mut buf = vec![0; 4 * MB]; + loop { + let percent = kernel.len() as u64 * 100 / len; + print!("\r{}% - {} MB", percent, kernel.len() / MB); + + let count = kernel_file.read(&mut buf)?; + if count == 0 { + break; + } + + kernel.extend(&buf[.. count]); + } + println!(""); + + (kernel, String::new()) + }; + + println!("Copying Kernel..."); + unsafe { + KERNEL_SIZE = kernel.len() as u64; + println!("Size: {}", KERNEL_SIZE); + KERNEL_ENTRY = *(kernel.as_ptr().offset(0x18) as *const u64); + println!("Entry: {:X}", KERNEL_ENTRY); + ptr::copy(kernel.as_ptr(), KERNEL_PHYSICAL as *mut u8, kernel.len()); + } + + println!("Done!"); + } + + unsafe { + let key = memory_map(); + exit_boot_services(key); + } + + unsafe { + asm!("msr daifset, #2"); + paging(); + } + + unsafe { + enter(); + } +} + +fn select_mode(output: &mut Output) -> Result<u32> { + loop { + for i in 0..output.0.Mode.MaxMode { + let mut mode_ptr = ::core::ptr::null_mut(); + let mut mode_size = 0; + (output.0.QueryMode)(output.0, i, &mut mode_size, &mut mode_ptr)?; + + let mode = unsafe { &mut *mode_ptr }; + let w = mode.HorizontalResolution; + let h = mode.VerticalResolution; + + print!("\r{}x{}: Is this OK? (y)es/(n)o", w, h); + + if key(true)? == Key::Character('y') { + println!(""); + + return Ok(i); + } + } + } +} + +fn pretty_pipe<T, F: FnMut() -> Result<T>>(splash: &Image, f: F) -> Result<T> { + let mut display = Display::new(Output::one()?); + + let mut display = ScaledDisplay::new(&mut display); + + { + let bg = Color::rgb(0x4a, 0xa3, 0xfd); + + display.set(bg); + + { + let x = (display.width() as i32 - splash.width() as i32)/2; + let y = 16; + splash.draw(&mut display, x, y); + } + + { + let prompt = format!( + "Redox Bootloader {} {}", + env!("CARGO_PKG_VERSION"), + env!("TARGET").split('-').next().unwrap_or("") + ); + let mut x = (display.width() as i32 - prompt.len() as i32 * 8)/2; + let y = display.height() as i32 - 32; + for c in prompt.chars() { + display.char(x, y, c, Color::rgb(0xff, 0xff, 0xff)); + x += 8; + } + } + + display.sync(); + } + + { + let cols = 80; + let off_x = (display.width() as i32 - cols as i32 * 8)/2; + let off_y = 16 + splash.height() as i32 + 16; + let rows = (display.height() as i32 - 64 - off_y - 1) as usize/16; + display.rect(off_x, off_y, cols as u32 * 8, rows as u32 * 16, Color::rgb(0, 0, 0)); + display.sync(); + + let mut text = TextDisplay::new(display); + text.off_x = off_x; + text.off_y = off_y; + text.cols = cols; + text.rows = rows; + text.pipe(f) + } +} + +pub fn main() -> Result<()> { + inner()?; + + /* TODO + if let Ok(mut output) = Output::one() { + let mut splash = Image::new(0, 0); + { + println!("Loading Splash..."); + if let Ok(image) = image::bmp::parse(&SPLASHBMP) { + splash = image; + } + println!(" Done"); + } + + /* TODO + let mode = pretty_pipe(&splash, || { + select_mode(&mut output) + })?; + (output.0.SetMode)(output.0, mode)?; + */ + + pretty_pipe(&splash, inner)?; + } else { + inner()?; + } + */ + + Ok(()) +} diff --git a/src/os/uefi/arch/aarch64/paging.rs b/src/os/uefi/arch/aarch64/paging.rs new file mode 100644 index 0000000000000000000000000000000000000000..eb473f8c6a126a8a47dc4cc23d473f8fe01a94d2 --- /dev/null +++ b/src/os/uefi/arch/aarch64/paging.rs @@ -0,0 +1,10 @@ +pub unsafe fn paging() { + // Disable MMU + asm!( + "mrs x0, sctlr_el1", + "bic x0, x0, 1", + "msr sctlr_el1, x0", + "isb", + lateout("x0") _ + ); +} diff --git a/src/os/uefi/arch/aarch64/partitions.rs b/src/os/uefi/arch/aarch64/partitions.rs new file mode 100644 index 0000000000000000000000000000000000000000..bc126631ef3b2159a444b39bea527b7864959fab --- /dev/null +++ b/src/os/uefi/arch/aarch64/partitions.rs @@ -0,0 +1,63 @@ +use std::proto::Protocol; + +#[repr(packed)] +#[derive(Clone, Copy, Debug)] +pub struct PartitionProtoInfoMbr { + pub boot: u8, + pub chs_start: [u8; 3], + pub ty: u8, + pub chs_end: [u8; 3], + pub start_lba: u32, + pub lba_size: u32, +} + +#[repr(packed)] +#[derive(Clone, Copy)] +pub struct PartitionProtoInfoGpt { + pub part_ty_guid: [u8; 16], + pub uniq_guid: [u8; 16], + pub start_lba: u64, + pub end_lba: u64, + pub attrs: u64, + pub name: [u16; 36], + // reserved until end of block +} + +#[repr(packed)] +#[derive(Clone, Copy)] +pub union PartitionProtoDataInfo { + pub mbr: PartitionProtoInfoMbr, + pub gpt: PartitionProtoInfoGpt, +} + +#[repr(packed)] +pub struct PartitionProtoData { + pub rev: u32, + pub ty: u32, + pub sys: u8, + pub resv: [u8; 7], + pub info: PartitionProtoDataInfo, +} + +pub const PARTITION_INFO_PROTOCOL_REVISION: u32 = 0x1000; +pub const ESP_GUID: [u8; 16] = [0x28, 0x73, 0x2a, 0xc1, 0x1f, 0xf8, 0xd2, 0x11, 0xba, 0x4b, 0x0, 0xa0, 0xc9, 0x3e, 0xc9, 0x3b]; // c12a7328-f81f-11d2-bA4b-00a0c93ec93b +pub const LINUX_FS_GUID: [u8; 16] = [0xaf, 0x3d, 0xc6, 0xf, 0x83, 0x84, 0x72, 0x47, 0x8e, 0x79, 0x3d, 0x69, 0xd8, 0x47, 0x7d, 0xe4]; // 0fc63daf-8483-4772-8e79-3d69d8477de4 +pub const REDOX_FS_GUID: [u8; 16] = [0xfd, 0x98, 0x78, 0x52, 0xe3, 0xff, 0xc2, 0x42, 0xe3, 0x96, 0x10, 0x5b, 0xa6, 0x3f, 0x5a, 0xbf]; // 527898fd-ffe3-42c2-96e3-bf5a3fa65b10 + +#[repr(u32)] +pub enum PartitionProtoDataTy { + Other = 0, + Mbr = 1, + Gpt = 2, +} + +pub struct PartitionProto(pub &'static mut PartitionProtoData); + +impl Protocol<PartitionProtoData> for PartitionProto { + fn guid() -> uefi::guid::Guid { + uefi::guid::Guid(0x8cf2f62c, 0xbc9b, 0x4821, [0x80, 0x8d, 0xec, 0x9e, 0xc4, 0x21, 0xa1, 0xa0]) + } + fn new(inner: &'static mut PartitionProtoData) -> Self { + Self(inner) + } +} diff --git a/src/os/uefi/arch/mod.rs b/src/os/uefi/arch/mod.rs new file mode 100644 index 0000000000000000000000000000000000000000..5b20243b93779aff629a4f7ed524e8ba65bb6e4e --- /dev/null +++ b/src/os/uefi/arch/mod.rs @@ -0,0 +1,9 @@ +#[cfg(target_arch = "aarch64")] +mod aarch64; +#[cfg(target_arch = "aarch64")] +pub use self::aarch64::*; + +#[cfg(target_arch = "x86_64")] +mod x86_64; +#[cfg(target_arch = "x86_64")] +pub use self::x86_64::*; diff --git a/src/os/uefi/arch/x86_64/memory_map.rs b/src/os/uefi/arch/x86_64/memory_map.rs new file mode 100644 index 0000000000000000000000000000000000000000..3b6e18f0fdc2a4137d353f568318a1c1abada6a1 --- /dev/null +++ b/src/os/uefi/arch/x86_64/memory_map.rs @@ -0,0 +1,80 @@ +use core::{mem, ptr}; +use uefi::memory::{MemoryDescriptor, MemoryType}; + +static MM_BASE: u64 = 0x500; +static MM_SIZE: u64 = 0x4B00; + +/// Memory does not exist +pub const MEMORY_AREA_NULL: u32 = 0; + +/// Memory is free to use +pub const MEMORY_AREA_FREE: u32 = 1; + +/// Memory is reserved +pub const MEMORY_AREA_RESERVED: u32 = 2; + +/// Memory is used by ACPI, and can be reclaimed +pub const MEMORY_AREA_ACPI: u32 = 3; + +/// A memory map area +#[derive(Copy, Clone, Debug, Default)] +#[repr(packed)] +pub struct MemoryArea { + pub base_addr: u64, + pub length: u64, + pub _type: u32, + pub acpi: u32 +} + +pub unsafe fn memory_map() -> usize { + let uefi = std::system_table(); + + ptr::write_bytes(MM_BASE as *mut u8, 0, MM_SIZE as usize); + + let mut map: [u8; 65536] = [0; 65536]; + let mut map_size = map.len(); + let mut map_key = 0; + let mut descriptor_size = 0; + let mut descriptor_version = 0; + let _ = (uefi.BootServices.GetMemoryMap)( + &mut map_size, + map.as_mut_ptr() as *mut MemoryDescriptor, + &mut map_key, + &mut descriptor_size, + &mut descriptor_version + ); + + if descriptor_size >= mem::size_of::<MemoryDescriptor>() { + for i in 0..map_size/descriptor_size { + let descriptor_ptr = map.as_ptr().offset((i * descriptor_size) as isize); + let descriptor = & *(descriptor_ptr as *const MemoryDescriptor); + let descriptor_type: MemoryType = mem::transmute(descriptor.Type); + + let bios_type = match descriptor_type { + MemoryType::EfiLoaderCode | + MemoryType::EfiLoaderData | + MemoryType::EfiBootServicesCode | + MemoryType::EfiBootServicesData | + MemoryType::EfiConventionalMemory => { + MEMORY_AREA_FREE + }, + _ => { + MEMORY_AREA_RESERVED + } + }; + + let bios_area = MemoryArea { + base_addr: descriptor.PhysicalStart.0, + length: descriptor.NumberOfPages * 4096, + _type: bios_type, + acpi: 0, + }; + + ptr::write((MM_BASE as *mut MemoryArea).offset(i as isize), bios_area); + } + } else { + println!("Unknown memory descriptor size: {}", descriptor_size); + } + + map_key +} diff --git a/src/os/uefi/arch/x86_64/mod.rs b/src/os/uefi/arch/x86_64/mod.rs new file mode 100644 index 0000000000000000000000000000000000000000..ad5ade1bfa36708bf377c9cdbc8314cafe10a85a --- /dev/null +++ b/src/os/uefi/arch/x86_64/mod.rs @@ -0,0 +1,530 @@ +use core::{cmp, mem, ptr, slice}; +use orbclient::{Color, Renderer}; +use std::fs::find; +use std::proto::Protocol; +use std::string::String; +use std::vec::Vec; +use uefi::status::{Error, Result}; +use uefi::guid::GuidKind; +use uefi::memory::MemoryType; + +use super::super::{ + disk::DiskEfi, + display::{Display, ScaledDisplay, Output}, + key::{key, Key}, + text::TextDisplay, +}; + +use self::memory_map::memory_map; +use self::paging::{paging_create, paging_enter}; + +mod memory_map; +mod paging; +mod partitions; + +static PHYS_OFFSET: u64 = 0xFFFF800000000000; + +static mut KERNEL_PHYS: u64 = 0; +static mut KERNEL_SIZE: u64 = 0; +static mut KERNEL_ENTRY: u64 = 0; + +static mut STACK_PHYS: u64 = 0; +static STACK_SIZE: u64 = 0x20000; + +static mut ENV_PHYS: u64 = 0; +static mut ENV_SIZE: u64 = 0; + +static mut RSDPS_AREA: Option<Vec<u8>> = None; + +#[repr(packed)] +pub struct KernelArgs { + kernel_base: u64, + kernel_size: u64, + stack_base: u64, + stack_size: u64, + env_base: u64, + env_size: u64, + + acpi_rsdps_base: u64, + acpi_rsdps_size: u64, +} + +unsafe fn allocate_zero_pages(pages: usize) -> Result<usize> { + let uefi = std::system_table(); + + let mut ptr = 0; + (uefi.BootServices.AllocatePages)( + 0, // AllocateAnyPages + MemoryType::EfiRuntimeServicesData, // Keeps this memory out of free space list + pages, + &mut ptr + )?; + + ptr::write_bytes(ptr as *mut u8, 0, 4096); + + Ok(ptr) +} + +unsafe fn exit_boot_services(key: usize) { + let handle = std::handle(); + let uefi = std::system_table(); + + let _ = (uefi.BootServices.ExitBootServices)(handle, key); +} + +unsafe fn enter() -> ! { + let args = KernelArgs { + kernel_base: KERNEL_PHYS, + kernel_size: KERNEL_SIZE, + stack_base: STACK_PHYS, + stack_size: STACK_SIZE, + env_base: ENV_PHYS, + env_size: ENV_SIZE, + acpi_rsdps_base: RSDPS_AREA.as_ref().map(Vec::as_ptr).unwrap_or(core::ptr::null()) as usize as u64 + PHYS_OFFSET, + acpi_rsdps_size: RSDPS_AREA.as_ref().map(Vec::len).unwrap_or(0) as u64, + }; + + let entry_fn: extern "sysv64" fn(args_ptr: *const KernelArgs) -> ! = mem::transmute(KERNEL_ENTRY); + entry_fn(&args); +} + +fn get_correct_block_io() -> Result<DiskEfi> { + // Get all BlockIo handles. + let mut handles = vec! [uefi::Handle(0); 128]; + let mut size = handles.len() * mem::size_of::<uefi::Handle>(); + + (std::system_table().BootServices.LocateHandle)(uefi::boot::LocateSearchType::ByProtocol, &uefi::guid::BLOCK_IO_GUID, 0, &mut size, handles.as_mut_ptr())?; + + let max_size = size / mem::size_of::<uefi::Handle>(); + let actual_size = std::cmp::min(handles.len(), max_size); + + // Return the handle that seems bootable. + for handle in handles.into_iter().take(actual_size) { + let block_io = DiskEfi::handle_protocol(handle)?; + if !block_io.0.Media.LogicalPartition { + continue; + } + + let part = partitions::PartitionProto::handle_protocol(handle)?.0; + if part.sys == 1 { + continue; + } + assert_eq!({part.rev}, partitions::PARTITION_INFO_PROTOCOL_REVISION); + if part.ty == partitions::PartitionProtoDataTy::Gpt as u32 { + let gpt = unsafe { part.info.gpt }; + assert_ne!(gpt.part_ty_guid, partitions::ESP_GUID, "detected esp partition again"); + if gpt.part_ty_guid == partitions::REDOX_FS_GUID || gpt.part_ty_guid == partitions::LINUX_FS_GUID { + return Ok(block_io); + } + } else if part.ty == partitions::PartitionProtoDataTy::Mbr as u32 { + let mbr = unsafe { part.info.mbr }; + if mbr.ty == 0x83 { + return Ok(block_io); + } + } else { + continue; + } + } + panic!("Couldn't find handle for partition"); +} + +struct Invalid; + +fn validate_rsdp(address: usize, v2: bool) -> core::result::Result<usize, Invalid> { + #[repr(packed)] + #[derive(Clone, Copy, Debug)] + struct Rsdp { + signature: [u8; 8], // b"RSD PTR " + chksum: u8, + oem_id: [u8; 6], + revision: u8, + rsdt_addr: u32, + // the following fields are only available for ACPI 2.0, and are reserved otherwise + length: u32, + xsdt_addr: u64, + extended_chksum: u8, + _rsvd: [u8; 3], + } + // paging is not enabled at this stage; we can just read the physical address here. + let rsdp_bytes = unsafe { core::slice::from_raw_parts(address as *const u8, core::mem::size_of::<Rsdp>()) }; + let rsdp = unsafe { (rsdp_bytes.as_ptr() as *const Rsdp).as_ref::<'static>().unwrap() }; + + println!("RSDP: {:?}", rsdp); + + if rsdp.signature != *b"RSD PTR " { + return Err(Invalid); + } + let mut base_sum = 0u8; + for base_byte in &rsdp_bytes[..20] { + base_sum = base_sum.wrapping_add(*base_byte); + } + if base_sum != 0 { + return Err(Invalid); + } + + if rsdp.revision == 2 { + let mut extended_sum = 0u8; + for byte in rsdp_bytes { + extended_sum = extended_sum.wrapping_add(*byte); + } + + if extended_sum != 0 { + return Err(Invalid); + } + } + + let length = if rsdp.revision == 2 { rsdp.length as usize } else { core::mem::size_of::<Rsdp>() }; + + Ok(length) +} + +fn find_acpi_table_pointers() -> Result<()> { + let rsdps_area = unsafe { + RSDPS_AREA = Some(Vec::new()); + RSDPS_AREA.as_mut().unwrap() + }; + + let cfg_tables = std::system_table().config_tables(); + + for (address, v2) in cfg_tables.iter().find_map(|cfg_table| if cfg_table.VendorGuid.kind() == GuidKind::Acpi { Some((cfg_table.VendorTable, false)) } else if cfg_table.VendorGuid.kind() == GuidKind::Acpi2 { Some((cfg_table.VendorTable, true)) } else { None }) { + match validate_rsdp(address, v2) { + Ok(length) => { + let align = 8; + + rsdps_area.extend(&u32::to_ne_bytes(length as u32)); + rsdps_area.extend(unsafe { core::slice::from_raw_parts(address as *const u8, length) }); + rsdps_area.resize(((rsdps_area.len() + (align - 1)) / align) * align, 0u8); + } + Err(_) => println!("Found RSDP that wasn't valid at {:p}", address as *const u8), + } + } + Ok(()) +} + +fn redoxfs() -> Result<redoxfs::FileSystem<DiskEfi>> { + // TODO: Scan multiple partitions for a kernel. + // TODO: pass block_opt for performance reasons + redoxfs::FileSystem::open(get_correct_block_io()?, None).map_err(|_| Error::DeviceError) +} + +const MB: usize = 1024 * 1024; + +fn inner() -> Result<()> { + //TODO: detect page size? + let page_size = 4096; + + { + let mut env = String::new(); + if let Ok(output) = Output::one() { + let mode = &output.0.Mode; + env.push_str(&format!("FRAMEBUFFER_ADDR={:016x}\n", mode.FrameBufferBase)); + env.push_str(&format!("FRAMEBUFFER_WIDTH={:016x}\n", mode.Info.HorizontalResolution)); + env.push_str(&format!("FRAMEBUFFER_HEIGHT={:016x}\n", mode.Info.VerticalResolution)); + } + + println!("Loading Kernel..."); + let kernel = { + let mut fs = redoxfs()?; + + let root = fs.header.1.root; + let node = fs.find_node("kernel", root).map_err(|_| Error::DeviceError)?; + + let len = fs.node_len(node.0).map_err(|_| Error::DeviceError)?; + + let kernel = unsafe { + let ptr = allocate_zero_pages((len as usize + page_size - 1) / page_size)?; + println!("{:X}", ptr); + + slice::from_raw_parts_mut( + ptr as *mut u8, + len as usize + ) + }; + + let mut i = 0; + for mut chunk in kernel.chunks_mut(4 * MB) { + print!("\r{}% - {} MB", i as u64 * 100 / len, i / MB); + + let count = fs.read_node(node.0, i as u64, &mut chunk, 0, 0).map_err(|_| Error::DeviceError)?; + if count == 0 { + break; + } + //TODO: return error instead of assert + assert_eq!(count, chunk.len()); + + i += count; + } + println!("\r{}% - {} MB", i as u64 * 100 / len, i / MB); + + env.push_str(&format!("REDOXFS_BLOCK={:016x}\n", fs.block)); + env.push_str("REDOXFS_UUID="); + for i in 0..fs.header.1.uuid.len() { + if i == 4 || i == 6 || i == 8 || i == 10 { + env.push('-'); + } + + env.push_str(&format!("{:>02x}", fs.header.1.uuid[i])); + } + + kernel + }; + + unsafe { + KERNEL_PHYS = kernel.as_ptr() as u64; + KERNEL_SIZE = kernel.len() as u64; + KERNEL_ENTRY = *(kernel.as_ptr().offset(0x18) as *const u64); + println!("Kernel {:X}:{:X} entry {:X}", KERNEL_PHYS, KERNEL_SIZE, KERNEL_ENTRY); + } + + println!("Allocating stack {:X}", STACK_SIZE); + unsafe { + STACK_PHYS = allocate_zero_pages(STACK_SIZE as usize / page_size)? as u64; + println!("Stack {:X}:{:X}", STACK_PHYS, STACK_SIZE); + } + + println!("Allocating env {:X}", env.len()); + unsafe { + ENV_PHYS = allocate_zero_pages((env.len() + page_size - 1) / page_size)? as u64; + ENV_SIZE = env.len() as u64; + ptr::copy(env.as_ptr(), ENV_PHYS as *mut u8, env.len()); + println!("Env {:X}:{:X}", ENV_PHYS, ENV_SIZE); + } + + println!("Parsing and writing ACPI RSDP structures."); + find_acpi_table_pointers(); + + println!("Done!"); + } + + println!("Creating page tables"); + let page_phys = unsafe { + paging_create(KERNEL_PHYS)? + }; + + println!("Entering kernel"); + unsafe { + let key = memory_map(); + exit_boot_services(key); + } + + unsafe { + llvm_asm!("cli" : : : "memory" : "intel", "volatile"); + paging_enter(page_phys); + } + + unsafe { + llvm_asm!("mov rsp, $0" : : "r"(STACK_PHYS + PHYS_OFFSET + STACK_SIZE) : "memory" : "intel", "volatile"); + enter(); + } +} + +fn draw_text(display: &mut ScaledDisplay, mut x: i32, y: i32, text: &str, color: Color) { + for c in text.chars() { + display.char(x, y, c, color); + x += 8; + } +} + +fn draw_background(display: &mut ScaledDisplay) { + let bg = Color::rgb(0x4a, 0xa3, 0xfd); + + display.set(bg); + + { + let prompt = format!( + "Redox Bootloader {} {}", + env!("CARGO_PKG_VERSION"), + env!("TARGET").split('-').next().unwrap_or("") + ); + let x = (display.width() as i32 - prompt.len() as i32 * 8)/2; + let y = display.height() as i32 - 32; + draw_text(display, x, y, &prompt, Color::rgb(0xff, 0xff, 0xff)); + } +} + +fn select_mode(output: &mut Output) -> Result<()> { + // Read all available modes + let mut modes = Vec::new(); + for i in 0..output.0.Mode.MaxMode { + let mut mode_ptr = ::core::ptr::null_mut(); + let mut mode_size = 0; + (output.0.QueryMode)(output.0, i, &mut mode_size, &mut mode_ptr)?; + + let mode = unsafe { &mut *mode_ptr }; + let w = mode.HorizontalResolution; + let h = mode.VerticalResolution; + + let mut aspect_w = w; + let mut aspect_h = h; + for i in 2..cmp::min(aspect_w / 2, aspect_h / 2) { + while aspect_w % i == 0 && aspect_h % i == 0 { + aspect_w /= i; + aspect_h /= i; + } + } + + //TODO: support resolutions that are not perfect multiples of 4 + if w % 4 != 0 { + continue; + } + + modes.push((i, w, h, format!("{:>4}x{:<4} {:>3}:{:<3}", w, h, aspect_w, aspect_h))); + } + + // Sort modes by pixel area, reversed + modes.sort_by(|a, b| (b.1 * b.2).cmp(&(a.1 * a.2))); + + // Find current mode index + let mut selected = output.0.Mode.Mode; + + // If there are no modes from querymode, don't change mode + if modes.is_empty() { + return Ok(()); + } + + let white = Color::rgb(0xff, 0xff, 0xff); + let black = Color::rgb(0x00, 0x00, 0x00); + let rows = 12; + loop { + { + // Create a scaled display + let mut display = Display::new(output); + let mut display = ScaledDisplay::new(&mut display); + + draw_background(&mut display); + + let off_x = (display.width() as i32 - 60 * 8)/2; + let mut off_y = 16; + draw_text( + &mut display, + off_x, off_y, + "Arrow keys and enter select mode", + white + ); + off_y += 24; + + let mut row = 0; + let mut col = 0; + for (i, w, h, text) in modes.iter() { + if row >= rows as i32 { + col += 1; + row = 0; + } + + let x = off_x + col * 20 * 8; + let y = off_y + row * 16; + + let fg = if *i == selected { + display.rect(x - 8, y, text.len() as u32 * 8 + 16, 16, white); + black + } else { + white + }; + + draw_text(&mut display, x, y, text, fg); + + row += 1; + } + + display.sync(); + } + + match key(true)? { + Key::Left => { + if let Some(mut mode_i) = modes.iter().position(|x| x.0 == selected) { + if mode_i < rows { + while mode_i < modes.len() { + mode_i += rows; + } + } + mode_i -= rows; + if let Some(new) = modes.get(mode_i) { + selected = new.0; + } + } + }, + Key::Right => { + if let Some(mut mode_i) = modes.iter().position(|x| x.0 == selected) { + mode_i += rows; + if mode_i >= modes.len() { + mode_i = mode_i % rows; + } + if let Some(new) = modes.get(mode_i) { + selected = new.0; + } + } + }, + Key::Up => { + if let Some(mut mode_i) = modes.iter().position(|x| x.0 == selected) { + if mode_i % rows == 0 { + mode_i += rows; + if mode_i > modes.len() { + mode_i = modes.len(); + } + } + mode_i -= 1; + if let Some(new) = modes.get(mode_i) { + selected = new.0; + } + } + }, + Key::Down => { + if let Some(mut mode_i) = modes.iter().position(|x| x.0 == selected) { + mode_i += 1; + if mode_i % rows == 0 { + mode_i -= rows; + } + if mode_i >= modes.len() { + mode_i = mode_i - mode_i % rows; + } + if let Some(new) = modes.get(mode_i) { + selected = new.0; + } + } + }, + Key::Enter => { + (output.0.SetMode)(output.0, selected)?; + return Ok(()); + }, + _ => (), + } + } +} + +fn pretty_pipe<T, F: FnMut() -> Result<T>>(output: &mut Output, f: F) -> Result<T> { + let mut display = Display::new(output); + + let mut display = ScaledDisplay::new(&mut display); + + draw_background(&mut display); + + display.sync(); + + { + let cols = 80; + let off_x = (display.width() as i32 - cols as i32 * 8)/2; + let off_y = 16; + let rows = (display.height() as i32 - 64 - off_y - 1) as usize/16; + display.rect(off_x, off_y, cols as u32 * 8, rows as u32 * 16, Color::rgb(0, 0, 0)); + display.sync(); + + let mut text = TextDisplay::new(display); + text.off_x = off_x; + text.off_y = off_y; + text.cols = cols; + text.rows = rows; + text.pipe(f) + } +} + +pub fn main() -> Result<()> { + if let Ok(mut output) = Output::one() { + select_mode(&mut output)?; + + pretty_pipe(&mut output, inner)?; + } else { + inner()?; + } + + Ok(()) +} diff --git a/src/os/uefi/arch/x86_64/paging.rs b/src/os/uefi/arch/x86_64/paging.rs new file mode 100644 index 0000000000000000000000000000000000000000..310efa76fd417e9f70d6c2e2d511b4891ccd802e --- /dev/null +++ b/src/os/uefi/arch/x86_64/paging.rs @@ -0,0 +1,101 @@ +use core::slice; +use x86::{ + controlregs::{self, Cr0, Cr4}, + msr, +}; +use uefi::status::Result; + +unsafe fn paging_allocate() -> Result<&'static mut [u64]> { + let ptr = super::allocate_zero_pages(1)?; + + Ok(slice::from_raw_parts_mut( + ptr as *mut u64, + 512 // page size divided by u64 size + )) +} + +pub unsafe fn paging_create(kernel_phys: u64) -> Result<u64> { + // Create PML4 + let pml4 = paging_allocate()?; + + // Recursive mapping for compatibility + pml4[511] = pml4.as_ptr() as u64 | 1 << 1 | 1; + + { + // Create PDP for identity mapping + let pdp = paging_allocate()?; + + // Link first user and first kernel PML4 entry to PDP + pml4[0] = pdp.as_ptr() as u64 | 1 << 1 | 1; + pml4[256] = pdp.as_ptr() as u64 | 1 << 1 | 1; + + // Identity map 8 GiB pages + for pdp_i in 0..8 { + let pd = paging_allocate()?; + pdp[pdp_i] = pd.as_ptr() as u64 | 1 << 1 | 1; + for pd_i in 0..pd.len() { + let pt = paging_allocate()?; + pd[pd_i] = pt.as_ptr() as u64 | 1 << 1 | 1; + for pt_i in 0..pt.len() { + let addr = + pdp_i as u64 * 0x4000_0000 + + pd_i as u64 * 0x20_0000 + + pt_i as u64 * 0x1000; + pt[pt_i] = addr | 1 << 1 | 1; + } + } + } + } + + { + // Create PDP for kernel mapping + let pdp = paging_allocate()?; + + // Link second to last PML4 entry to PDP + pml4[510] = pdp.as_ptr() as u64 | 1 << 1 | 1; + + // Map 1 GiB at kernel offset + for pdp_i in 0..1 { + let pd = paging_allocate()?; + pdp[pdp_i] = pd.as_ptr() as u64 | 1 << 1 | 1; + for pd_i in 0..pd.len() { + let pt = paging_allocate()?; + pd[pd_i] = pt.as_ptr() as u64 | 1 << 1 | 1; + for pt_i in 0..pt.len() { + let addr = + pdp_i as u64 * 0x4000_0000 + + pd_i as u64 * 0x20_0000 + + pt_i as u64 * 0x1000 + + kernel_phys; + pt[pt_i] = addr | 1 << 1 | 1; + } + } + } + } + + Ok(pml4.as_ptr() as u64) +} + +pub unsafe fn paging_enter(page_phys: u64) { + // Enable OSXSAVE, FXSAVE/FXRSTOR, Page Global, Page Address Extension, and Page Size Extension + let mut cr4 = controlregs::cr4(); + cr4 |= Cr4::CR4_ENABLE_OS_XSAVE + | Cr4::CR4_ENABLE_SSE + | Cr4::CR4_ENABLE_GLOBAL_PAGES + | Cr4::CR4_ENABLE_PAE + | Cr4::CR4_ENABLE_PSE; + controlregs::cr4_write(cr4); + + // Enable Long mode and NX bit + let mut efer = msr::rdmsr(msr::IA32_EFER); + efer |= 1 << 11 | 1 << 8; + msr::wrmsr(msr::IA32_EFER, efer); + + // Set new page map + controlregs::cr3_write(page_phys); + + // Enable paging, write protect kernel, protected mode + let mut cr0 = controlregs::cr0(); + cr0 |= Cr0::CR0_ENABLE_PAGING | Cr0::CR0_WRITE_PROTECT | Cr0::CR0_PROTECTED_MODE; + controlregs::cr0_write(cr0); +} diff --git a/src/os/uefi/arch/x86_64/partitions.rs b/src/os/uefi/arch/x86_64/partitions.rs new file mode 100644 index 0000000000000000000000000000000000000000..bc126631ef3b2159a444b39bea527b7864959fab --- /dev/null +++ b/src/os/uefi/arch/x86_64/partitions.rs @@ -0,0 +1,63 @@ +use std::proto::Protocol; + +#[repr(packed)] +#[derive(Clone, Copy, Debug)] +pub struct PartitionProtoInfoMbr { + pub boot: u8, + pub chs_start: [u8; 3], + pub ty: u8, + pub chs_end: [u8; 3], + pub start_lba: u32, + pub lba_size: u32, +} + +#[repr(packed)] +#[derive(Clone, Copy)] +pub struct PartitionProtoInfoGpt { + pub part_ty_guid: [u8; 16], + pub uniq_guid: [u8; 16], + pub start_lba: u64, + pub end_lba: u64, + pub attrs: u64, + pub name: [u16; 36], + // reserved until end of block +} + +#[repr(packed)] +#[derive(Clone, Copy)] +pub union PartitionProtoDataInfo { + pub mbr: PartitionProtoInfoMbr, + pub gpt: PartitionProtoInfoGpt, +} + +#[repr(packed)] +pub struct PartitionProtoData { + pub rev: u32, + pub ty: u32, + pub sys: u8, + pub resv: [u8; 7], + pub info: PartitionProtoDataInfo, +} + +pub const PARTITION_INFO_PROTOCOL_REVISION: u32 = 0x1000; +pub const ESP_GUID: [u8; 16] = [0x28, 0x73, 0x2a, 0xc1, 0x1f, 0xf8, 0xd2, 0x11, 0xba, 0x4b, 0x0, 0xa0, 0xc9, 0x3e, 0xc9, 0x3b]; // c12a7328-f81f-11d2-bA4b-00a0c93ec93b +pub const LINUX_FS_GUID: [u8; 16] = [0xaf, 0x3d, 0xc6, 0xf, 0x83, 0x84, 0x72, 0x47, 0x8e, 0x79, 0x3d, 0x69, 0xd8, 0x47, 0x7d, 0xe4]; // 0fc63daf-8483-4772-8e79-3d69d8477de4 +pub const REDOX_FS_GUID: [u8; 16] = [0xfd, 0x98, 0x78, 0x52, 0xe3, 0xff, 0xc2, 0x42, 0xe3, 0x96, 0x10, 0x5b, 0xa6, 0x3f, 0x5a, 0xbf]; // 527898fd-ffe3-42c2-96e3-bf5a3fa65b10 + +#[repr(u32)] +pub enum PartitionProtoDataTy { + Other = 0, + Mbr = 1, + Gpt = 2, +} + +pub struct PartitionProto(pub &'static mut PartitionProtoData); + +impl Protocol<PartitionProtoData> for PartitionProto { + fn guid() -> uefi::guid::Guid { + uefi::guid::Guid(0x8cf2f62c, 0xbc9b, 0x4821, [0x80, 0x8d, 0xec, 0x9e, 0xc4, 0x21, 0xa1, 0xa0]) + } + fn new(inner: &'static mut PartitionProtoData) -> Self { + Self(inner) + } +} diff --git a/src/os/uefi/disk.rs b/src/os/uefi/disk.rs new file mode 100644 index 0000000000000000000000000000000000000000..2e58d0322eb6dc7847c53236c08b840c4a30988b --- /dev/null +++ b/src/os/uefi/disk.rs @@ -0,0 +1,44 @@ +use core::ops::{ControlFlow, Try}; +use redoxfs::{BLOCK_SIZE, Disk}; +use syscall::{EIO, Error, Result}; +use std::proto::Protocol; +use uefi::guid::{Guid, BLOCK_IO_GUID}; +use uefi::block_io::BlockIo as UefiBlockIo; + +pub struct DiskEfi(pub &'static mut UefiBlockIo); + +impl Protocol<UefiBlockIo> for DiskEfi { + fn guid() -> Guid { + BLOCK_IO_GUID + } + + fn new(inner: &'static mut UefiBlockIo) -> Self { + Self(inner) + } +} + +impl Disk for DiskEfi { + fn read_at(&mut self, block: u64, buffer: &mut [u8]) -> Result<usize> { + let block_size = self.0.Media.BlockSize as u64; + + let lba = block * BLOCK_SIZE / block_size; + + match (self.0.ReadBlocks)(self.0, self.0.Media.MediaId, lba, buffer.len(), buffer.as_mut_ptr()).branch() { + ControlFlow::Continue(_) => Ok(buffer.len()), + ControlFlow::Break(err) => { + println!("DiskEfi::read_at 0x{:X} failed: {:?}", block, err); + Err(Error::new(EIO)) + } + } + } + + fn write_at(&mut self, block: u64, _buffer: &[u8]) -> Result<usize> { + println!("DiskEfi::write_at 0x{:X} not implemented", block); + Err(Error::new(EIO)) + } + + fn size(&mut self) -> Result<u64> { + println!("DiskEfi::size not implemented"); + Err(Error::new(EIO)) + } +} diff --git a/src/os/uefi/display.rs b/src/os/uefi/display.rs new file mode 100644 index 0000000000000000000000000000000000000000..8a6521cf43848e601f0c7a9e95e529ffc010a154 --- /dev/null +++ b/src/os/uefi/display.rs @@ -0,0 +1,188 @@ +use core::cell::Cell; +use core::ops::Try; +use core::ptr; +use orbclient::{Color, Mode, Renderer}; +use std::boxed::Box; +use std::proto::Protocol; +use uefi::graphics::{GraphicsOutput, GraphicsBltOp, GraphicsBltPixel}; +use uefi::guid::{Guid, GRAPHICS_OUTPUT_PROTOCOL_GUID}; + +pub struct Output(pub &'static mut GraphicsOutput); + +impl Protocol<GraphicsOutput> for Output { + fn guid() -> Guid { + GRAPHICS_OUTPUT_PROTOCOL_GUID + } + + fn new(inner: &'static mut GraphicsOutput) -> Self { + Output(inner) + } +} + +pub struct Display<'a> { + output: &'a mut Output, + w: u32, + h: u32, + data: Box<[Color]>, + mode: Cell<Mode>, +} + +impl<'a> Display<'a> { + pub fn new(output: &'a mut Output) -> Self { + let w = output.0.Mode.Info.HorizontalResolution; + let h = output.0.Mode.Info.VerticalResolution; + Self { + output: output, + w: w, + h: h, + data: vec![Color::rgb(0, 0, 0); w as usize * h as usize].into_boxed_slice(), + mode: Cell::new(Mode::Blend), + } + } + + pub fn blit(&mut self, x: i32, y: i32, w: u32, h: u32) -> bool { + let status = (self.output.0.Blt)( + self.output.0, + self.data.as_mut_ptr() as *mut GraphicsBltPixel, + GraphicsBltOp::BufferToVideo, + x as usize, + y as usize, + x as usize, + y as usize, + w as usize, + h as usize, + 0 + ); + status.branch().is_continue() + } + + pub fn scroll(&mut self, rows: usize, color: Color) { + let width = self.w as usize; + let height = self.h as usize; + if rows > 0 && rows < height { + let off1 = rows * width; + let off2 = height * width - off1; + unsafe { + let data_ptr = self.data.as_mut_ptr() as *mut u32; + ptr::copy(data_ptr.offset(off1 as isize), data_ptr, off2 as usize); + for i in off2..off2 + off1 { + *data_ptr.add(i) = color.data; + } + } + } + } +} + +impl<'a> Renderer for Display<'a> { + fn width(&self) -> u32 { + self.w + } + + fn height(&self) -> u32 { + self.h + } + + fn data(&self) -> &[Color] { + &self.data + } + + fn data_mut(&mut self) -> &mut [Color] { + &mut self.data + } + + fn sync(&mut self) -> bool { + let w = self.width(); + let h = self.height(); + self.blit(0, 0, w, h) + } + + fn mode(&self) -> &Cell<Mode> { + &self.mode + } +} + +pub struct ScaledDisplay<'a> { + display: &'a mut Display<'a>, + scale: u32, +} + +impl<'a> ScaledDisplay<'a> { + pub fn new(display: &'a mut Display<'a>) -> Self { + let scale = if display.height() > 1440 { + 2 + } else { + 1 + }; + + Self { + display, + scale, + } + } + + pub fn scale(&self) -> u32 { + self.scale + } + + pub fn scroll(&mut self, rows: usize, color: Color) { + let scale = self.scale as usize; + self.display.scroll(rows * scale, color); + } + + pub fn blit(&mut self, x: i32, y: i32, w: u32, h: u32) -> bool { + let scale = self.scale; + self.display.blit( + x * scale as i32, + y * scale as i32, + w * scale, + h * scale + ) + } +} + +impl<'a> Renderer for ScaledDisplay<'a> { + fn width(&self) -> u32 { + self.display.width()/self.scale + } + + fn height(&self) -> u32 { + self.display.height()/self.scale + } + + fn data(&self) -> &[Color] { + unreachable!() + } + + fn data_mut(&mut self) -> &mut [Color] { + unreachable!() + } + + fn sync(&mut self) -> bool { + self.display.sync() + } + + fn pixel(&mut self, x: i32, y: i32, color: Color) { + self.rect(x, y, 1, 1, color); + } + + fn rect(&mut self, x: i32, y: i32, w: u32, h: u32, color: Color) { + let scale = self.scale; + self.display.rect( + x * scale as i32, + y * scale as i32, + w * scale, + h * scale, + color + ); + } + + fn set(&mut self, color: Color) { + let w = self.width(); + let h = self.height(); + self.rect(0, 0, w, h, color); + } + + fn mode(&self) -> &Cell<Mode> { + self.display.mode() + } +} diff --git a/src/os/uefi/key.rs b/src/os/uefi/key.rs new file mode 100644 index 0000000000000000000000000000000000000000..d1a97e6e25a9002040444cfc35b8c4aad6db3b6b --- /dev/null +++ b/src/os/uefi/key.rs @@ -0,0 +1,95 @@ +use core::char; +use uefi::status::Result; +use uefi::text::TextInputKey; + +#[derive(Debug, PartialEq)] +pub enum Key { + Backspace, + Tab, + Enter, + Character(char), + Up, + Down, + Right, + Left, + Home, + End, + Insert, + Delete, + PageUp, + PageDown, + F1, + F2, + F3, + F4, + F5, + F6, + F7, + F8, + F9, + F10, + F11, + F12, + Escape, + Scancode(u16), +} + +impl From<TextInputKey> for Key { + fn from(raw_key: TextInputKey) -> Self { + match raw_key.ScanCode { + 0 => match unsafe { char::from_u32_unchecked(raw_key.UnicodeChar as u32) } { + '\u{8}' => Key::Backspace, + '\t' => Key::Tab, + '\r' => Key::Enter, + c => Key::Character(c), + }, + 1 => Key::Up, + 2 => Key::Down, + 3 => Key::Right, + 4 => Key::Left, + 5 => Key::Home, + 6 => Key::End, + 7 => Key::Insert, + 8 => Key::Delete, + 9 => Key::PageUp, + 10 => Key::PageDown, + 11 => Key::F1, + 12 => Key::F2, + 13 => Key::F3, + 14 => Key::F4, + 15 => Key::F5, + 16 => Key::F6, + 17 => Key::F7, + 18 => Key::F8, + 19 => Key::F9, + 20 => Key::F10, + 21 => Key::F11, + 22 => Key::F12, + 23 => Key::Escape, + scancode => Key::Scancode(scancode), + } + } +} + +pub fn raw_key(wait: bool) -> Result<TextInputKey> { + let uefi = std::system_table(); + + if wait { + let mut index = 0; + (uefi.BootServices.WaitForEvent)(1, &uefi.ConsoleIn.WaitForKey, &mut index)?; + } + + let mut key = TextInputKey { + ScanCode: 0, + UnicodeChar: 0 + }; + + (uefi.ConsoleIn.ReadKeyStroke)(uefi.ConsoleIn, &mut key)?; + + Ok(key) +} + +pub fn key(wait: bool) -> Result<Key> { + let raw_key = raw_key(wait)?; + Ok(Key::from(raw_key)) +} diff --git a/src/os/uefi/mod.rs b/src/os/uefi/mod.rs index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..46bcd06347c61f3baddbf9c335a9b5b0388f9240 100644 --- a/src/os/uefi/mod.rs +++ b/src/os/uefi/mod.rs @@ -0,0 +1,53 @@ +use core::ops::Try; +use core::ptr; +use uefi::reset::ResetType; +use uefi::status::{Result, Status}; + +mod arch; +mod disk; +mod display; +mod key; +pub mod null; +pub mod text; + +fn set_max_mode(output: &uefi::text::TextOutput) -> Result<()> { + let mut max_i = None; + let mut max_w = 0; + let mut max_h = 0; + + for i in 0..output.Mode.MaxMode as usize { + let mut w = 0; + let mut h = 0; + if (output.QueryMode)(output, i, &mut w, &mut h).branch().is_continue() { + if w >= max_w && h >= max_h { + max_i = Some(i); + max_w = w; + max_h = h; + } + } + } + + if let Some(i) = max_i { + (output.SetMode)(output, i)?; + } + + Ok(()) +} + +#[no_mangle] +pub extern "C" fn main() -> Status { + let uefi = std::system_table(); + + let _ = (uefi.BootServices.SetWatchdogTimer)(0, 0, 0, ptr::null()); + + if let Err(err) = set_max_mode(uefi.ConsoleOut) { + println!("Failed to set max mode: {:?}", err); + } + + if let Err(err) = arch::main() { + println!("App error: {:?}", err); + let _ = key::key(true); + } + + (uefi.RuntimeServices.ResetSystem)(ResetType::Cold, Status(0), 0, ptr::null()); +} diff --git a/src/os/uefi/null.rs b/src/os/uefi/null.rs new file mode 100644 index 0000000000000000000000000000000000000000..1656d5576f950784a8470b0381d543ef2f4e2927 --- /dev/null +++ b/src/os/uefi/null.rs @@ -0,0 +1,128 @@ +use core::mem; +use core::ops::Deref; +use std::boxed::Box; +use uefi::Handle; +use uefi::boot::InterfaceType; +use uefi::guid::SIMPLE_TEXT_OUTPUT_GUID; +use uefi::status::{Result, Status}; +use uefi::text::TextOutputMode; + +#[repr(C)] +#[allow(non_snake_case)] +pub struct NullDisplay { + pub Reset: extern "win64" fn(&mut NullDisplay, bool) -> Status, + pub OutputString: extern "win64" fn(&mut NullDisplay, *const u16) -> Status, + pub TestString: extern "win64" fn(&mut NullDisplay, *const u16) -> Status, + pub QueryMode: extern "win64" fn(&mut NullDisplay, usize, &mut usize, &mut usize) -> Status, + pub SetMode: extern "win64" fn(&mut NullDisplay, usize) -> Status, + pub SetAttribute: extern "win64" fn(&mut NullDisplay, usize) -> Status, + pub ClearScreen: extern "win64" fn(&mut NullDisplay) -> Status, + pub SetCursorPosition: extern "win64" fn(&mut NullDisplay, usize, usize) -> Status, + pub EnableCursor: extern "win64" fn(&mut NullDisplay, bool) -> Status, + pub Mode: &'static TextOutputMode, + + pub mode: Box<TextOutputMode>, +} + +extern "win64" fn reset(_output: &mut NullDisplay, _extra: bool) -> Status { + Status(0) +} + +extern "win64" fn output_string(_output: &mut NullDisplay, _string: *const u16) -> Status { + Status(0) +} + +extern "win64" fn test_string(_output: &mut NullDisplay, _string: *const u16) -> Status { + Status(0) +} + +extern "win64" fn query_mode(_output: &mut NullDisplay, _mode: usize, columns: &mut usize, rows: &mut usize) -> Status { + *columns = 80; + *rows = 30; + Status(0) +} + +extern "win64" fn set_mode(_output: &mut NullDisplay, _mode: usize) -> Status { + Status(0) +} + +extern "win64" fn set_attribute(output: &mut NullDisplay, attribute: usize) -> Status { + output.mode.Attribute = attribute as i32; + Status(0) +} + +extern "win64" fn clear_screen(_output: &mut NullDisplay) -> Status { + Status(0) +} + +extern "win64" fn set_cursor_position(output: &mut NullDisplay, column: usize, row: usize) -> Status { + output.mode.CursorColumn = column as i32; + output.mode.CursorRow = row as i32; + Status(0) +} + +extern "win64" fn enable_cursor(output: &mut NullDisplay, enable: bool) -> Status { + output.mode.CursorVisible = enable; + Status(0) +} + +impl NullDisplay { + pub fn new() -> NullDisplay { + let mode = Box::new(TextOutputMode { + MaxMode: 0, + Mode: 0, + Attribute: 0, + CursorColumn: 0, + CursorRow: 0, + CursorVisible: false, + }); + + NullDisplay { + Reset: reset, + OutputString: output_string, + TestString: test_string, + QueryMode: query_mode, + SetMode: set_mode, + SetAttribute: set_attribute, + ClearScreen: clear_screen, + SetCursorPosition: set_cursor_position, + EnableCursor: enable_cursor, + Mode: unsafe { mem::transmute(&*mode.deref()) }, + + mode: mode + } + } + + pub fn pipe<T, F: FnMut() -> Result<T>>(&mut self, mut f: F) -> Result<T> { + let uefi = unsafe { std::system_table_mut() }; + + let stdout = self as *mut _; + let mut stdout_handle = Handle(0); + (uefi.BootServices.InstallProtocolInterface)(&mut stdout_handle, &SIMPLE_TEXT_OUTPUT_GUID, InterfaceType::Native, stdout as usize)?; + + let old_stdout_handle = uefi.ConsoleOutHandle; + let old_stdout = uefi.ConsoleOut as *mut _; + let old_stderr_handle = uefi.ConsoleErrorHandle; + let old_stderr = uefi.ConsoleError as *mut _; + + uefi.ConsoleOutHandle = stdout_handle; + uefi.ConsoleOut = unsafe { mem::transmute(&mut *stdout) }; + uefi.ConsoleErrorHandle = stdout_handle; + uefi.ConsoleError = unsafe { mem::transmute(&mut *stdout) }; + + let res = f(); + + uefi.ConsoleOutHandle = old_stdout_handle; + uefi.ConsoleOut = unsafe { mem::transmute(&mut *old_stdout) }; + uefi.ConsoleErrorHandle = old_stderr_handle; + uefi.ConsoleError = unsafe { mem::transmute(&mut *old_stderr) }; + + let _ = (uefi.BootServices.UninstallProtocolInterface)(stdout_handle, &SIMPLE_TEXT_OUTPUT_GUID, stdout as usize); + + res + } +} + +pub fn pipe<T, F: FnMut() -> Result<T>>(f: F) -> Result<T> { + NullDisplay::new().pipe(f) +} diff --git a/src/os/uefi/text.rs b/src/os/uefi/text.rs new file mode 100644 index 0000000000000000000000000000000000000000..5ebc6d656d6d62fb4dadb1185480881ba19d0132 --- /dev/null +++ b/src/os/uefi/text.rs @@ -0,0 +1,252 @@ +use core::{char, mem, ptr}; +use core::ops::Deref; +use orbclient::{Color, Renderer}; +use std::boxed::Box; +use std::proto::Protocol; +use uefi::Handle; +use uefi::boot::InterfaceType; +use uefi::guid::SIMPLE_TEXT_OUTPUT_GUID; +use uefi::status::{Result, Status}; +use uefi::text::TextOutputMode; + +use super::display::{Display, ScaledDisplay, Output}; + +#[repr(C)] +#[allow(non_snake_case)] +pub struct TextDisplay<'a> { + pub Reset: extern "win64" fn(&mut TextDisplay, bool) -> Status, + pub OutputString: extern "win64" fn(&mut TextDisplay, *const u16) -> Status, + pub TestString: extern "win64" fn(&mut TextDisplay, *const u16) -> Status, + pub QueryMode: extern "win64" fn(&mut TextDisplay, usize, &mut usize, &mut usize) -> Status, + pub SetMode: extern "win64" fn(&mut TextDisplay, usize) -> Status, + pub SetAttribute: extern "win64" fn(&mut TextDisplay, usize) -> Status, + pub ClearScreen: extern "win64" fn(&mut TextDisplay) -> Status, + pub SetCursorPosition: extern "win64" fn(&mut TextDisplay, usize, usize) -> Status, + pub EnableCursor: extern "win64" fn(&mut TextDisplay, bool) -> Status, + pub Mode: &'static TextOutputMode, + + pub mode: Box<TextOutputMode>, + pub off_x: i32, + pub off_y: i32, + pub cols: usize, + pub rows: usize, + pub display: ScaledDisplay<'a>, +} + +extern "win64" fn reset(_output: &mut TextDisplay, _extra: bool) -> Status { + Status(0) +} + +extern "win64" fn output_string(output: &mut TextDisplay, string: *const u16) -> Status { + output.write(string); + Status(0) +} + +extern "win64" fn test_string(_output: &mut TextDisplay, _string: *const u16) -> Status { + Status(0) +} + +extern "win64" fn query_mode(output: &mut TextDisplay, _mode: usize, columns: &mut usize, rows: &mut usize) -> Status { + *columns = output.cols; + *rows = output.rows; + Status(0) +} + +extern "win64" fn set_mode(_output: &mut TextDisplay, _mode: usize) -> Status { + Status(0) +} + +extern "win64" fn set_attribute(output: &mut TextDisplay, attribute: usize) -> Status { + output.mode.Attribute = attribute as i32; + Status(0) +} + +extern "win64" fn clear_screen(output: &mut TextDisplay) -> Status { + output.clear(); + Status(0) +} + +extern "win64" fn set_cursor_position(output: &mut TextDisplay, column: usize, row: usize) -> Status { + output.set_cursor_pos(column as i32, row as i32); + Status(0) +} + +extern "win64" fn enable_cursor(output: &mut TextDisplay, enable: bool) -> Status { + output.mode.CursorVisible = enable; + Status(0) +} + +impl<'a> TextDisplay<'a> { + pub fn new(display: ScaledDisplay<'a>) -> TextDisplay<'a> { + let mode = Box::new(TextOutputMode { + MaxMode: 0, + Mode: 0, + Attribute: 0, + CursorColumn: 0, + CursorRow: 0, + CursorVisible: false, + }); + + let cols = display.width() as usize/8; + let rows = display.height() as usize/16; + + TextDisplay { + Reset: reset, + OutputString: output_string, + TestString: test_string, + QueryMode: query_mode, + SetMode: set_mode, + SetAttribute: set_attribute, + ClearScreen: clear_screen, + SetCursorPosition: set_cursor_position, + EnableCursor: enable_cursor, + Mode: unsafe { mem::transmute(&*mode.deref()) }, + + mode, + off_x: 0, + off_y: 0, + cols, + rows, + display, + } + } + + pub fn pos(&self) -> (i32, i32) { + ( + self.mode.CursorColumn * 8 + self.off_x, + self.mode.CursorRow * 16 + self.off_y, + ) + } + + pub fn clear(&mut self) { + // Clears are ignored + //let bg = Color::rgb(0, 0, 0); + //self.display.rect(self.off_x, self.off_y, self.cols * 8, self.rows * 16, bg); + //self.display.blit(0, self.off_y, w, self.rows * 16); + self.display.sync(); + } + + pub fn scroll(&mut self, color: Color) { + if self.rows > 0 { + let w = self.display.width(); + + let dst = self.off_y * w as i32; + let src = (self.off_y + 16) * w as i32; + let len = (self.rows - 1) * 16 * w as usize; + unsafe { + let scale = self.display.scale() as isize; + let data_ptr = self.display.data_mut().as_mut_ptr() as *mut u32; + ptr::copy( + data_ptr.offset(src as isize * scale * scale), + data_ptr.offset(dst as isize * scale * scale), + len * (scale * scale) as usize); + } + + self.display.rect(self.off_x, self.off_y + (self.rows as i32 - 1) * 16, self.cols as u32 * 8, 16, color); + } + } + + pub fn set_cursor_pos(&mut self, column: i32, _row: i32) { + self.mode.CursorColumn = column; + } + + pub fn write(&mut self, string: *const u16) { + let bg = Color::rgb(0, 0, 0); + let fg = Color::rgb(255, 255, 255); + + let mut scrolled = false; + let mut changed = false; + let (_sx, sy) = self.pos(); + + let mut i = 0; + loop { + let w = unsafe { *string.offset(i) }; + if w == 0 { + break; + } + + let c = unsafe { char::from_u32_unchecked(w as u32) }; + + if self.mode.CursorColumn as usize >= self.cols { + self.mode.CursorColumn = 0; + self.mode.CursorRow += 1; + } + + while self.mode.CursorRow as usize >= self.rows { + self.scroll(bg); + self.mode.CursorRow -= 1; + scrolled = true; + } + + match c { + '\x08' => if self.mode.CursorColumn > 0 { + let (x, y) = self.pos(); + self.display.rect(x, y, 8, 16, bg); + self.mode.CursorColumn -= 1; + changed = true; + }, + '\r'=> { + self.mode.CursorColumn = 0; + }, + '\n' => { + self.mode.CursorRow += 1; + }, + _ => { + let (x, y) = self.pos(); + self.display.rect(x, y, 8, 16, bg); + self.display.char(x, y, c, fg); + self.mode.CursorColumn += 1; + changed = true; + } + } + + i += 1; + } + + if scrolled { + let (cx, cw) = (0, self.display.width() as i32); + let (cy, ch) = (self.off_y, self.rows as u32 * 16); + self.display.blit(cx, cy, cw as u32, ch as u32); + } else if changed { + let (_x, y) = self.pos(); + let (cx, cw) = (0, self.display.width() as i32); + let (cy, ch) = (sy, y + 16 - sy); + self.display.blit(cx, cy, cw as u32, ch as u32); + } + } + + pub fn pipe<T, F: FnMut() -> Result<T>>(&mut self, mut f: F) -> Result<T> { + let uefi = unsafe { std::system_table_mut() }; + + let stdout = self as *mut _; + let mut stdout_handle = Handle(0); + (uefi.BootServices.InstallProtocolInterface)(&mut stdout_handle, &SIMPLE_TEXT_OUTPUT_GUID, InterfaceType::Native, stdout as usize)?; + + let old_stdout_handle = uefi.ConsoleOutHandle; + let old_stdout = uefi.ConsoleOut as *mut _; + let old_stderr_handle = uefi.ConsoleErrorHandle; + let old_stderr = uefi.ConsoleError as *mut _; + + uefi.ConsoleOutHandle = stdout_handle; + uefi.ConsoleOut = unsafe { mem::transmute(&mut *stdout) }; + uefi.ConsoleErrorHandle = stdout_handle; + uefi.ConsoleError = unsafe { mem::transmute(&mut *stdout) }; + + let res = f(); + + uefi.ConsoleOutHandle = old_stdout_handle; + uefi.ConsoleOut = unsafe { mem::transmute(&mut *old_stdout) }; + uefi.ConsoleErrorHandle = old_stderr_handle; + uefi.ConsoleError = unsafe { mem::transmute(&mut *old_stderr) }; + + let _ = (uefi.BootServices.UninstallProtocolInterface)(stdout_handle, &SIMPLE_TEXT_OUTPUT_GUID, stdout as usize); + + res + } +} + +pub fn pipe<T, F: FnMut() -> Result<T>>(f: F) -> Result<T> { + let mut output = Output::one()?; + let mut display = Display::new(&mut output); + TextDisplay::new(ScaledDisplay::new(&mut display)).pipe(f) +}