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