diff --git a/mk/aarch64-unknown-uefi.mk b/mk/aarch64-unknown-uefi.mk
index 283e6a5f2a3b499004a769ff40adda65a72ba19d..7cd3d959420d38baac107722272584a4991cb057 100644
--- a/mk/aarch64-unknown-uefi.mk
+++ b/mk/aarch64-unknown-uefi.mk
@@ -56,7 +56,6 @@ $(BUILD)/firmware.rom:
 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 \
diff --git a/mk/x86-unknown-none.mk b/mk/x86-unknown-none.mk
index 91bd64de41086621700d192c416b4617736ea2de..790d2747e08066286e8d32ec7036ae2dc74cb8dd 100644
--- a/mk/x86-unknown-none.mk
+++ b/mk/x86-unknown-none.mk
@@ -47,7 +47,6 @@ $(BUILD)/harddrive.bin: $(BUILD)/bootloader.bin $(BUILD)/filesystem.bin
 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 \
diff --git a/mk/x86_64-unknown-uefi.mk b/mk/x86_64-unknown-uefi.mk
index ee41d971bfc0f58868d0b2577020c2776289e0ef..49332e47d602a7aa5533b4b525904b07588c93ae 100644
--- a/mk/x86_64-unknown-uefi.mk
+++ b/mk/x86_64-unknown-uefi.mk
@@ -56,7 +56,6 @@ $(BUILD)/firmware.rom:
 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 \
diff --git a/src/main.rs b/src/main.rs
index 39451625c52bae1e283a70d9d67bc6b83da86be4..ecd9c8187dcc9216412af6b1d7cf5e21328c654f 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -59,7 +59,6 @@ impl<'a> Write for SliceWriter<'a> {
 }
 
 #[allow(dead_code)]
-#[derive(Debug)]
 #[repr(packed)]
 pub struct KernelArgs {
     kernel_base: u64,
@@ -81,25 +80,11 @@ pub struct KernelArgs {
     acpi_rsdps_size: u64,
 }
 
-fn main<
+fn select_mode<
     D: Disk,
     M: Iterator<Item=OsMemoryEntry>,
     V: Iterator<Item=OsVideoMode>
->(os: &mut dyn Os<D, M, V>) -> (usize, KernelArgs) {
-    println!("Redox OS Bootloader {} on {}", env!("CARGO_PKG_VERSION"), os.name());
-
-    let mut fs = os.filesystem();
-
-    print!("RedoxFS ");
-    for i in 0..fs.header.1.uuid.len() {
-        if i == 4 || i == 6 || i == 8 || i == 10 {
-            print!("-");
-        }
-
-        print!("{:>02x}", fs.header.1.uuid[i]);
-    }
-    println!(": {} MiB", fs.header.1.size / MIBI as u64);
-
+>(os: &mut dyn Os<D, M, V>) -> Option<OsVideoMode> {
     let mut modes = Vec::new();
     for mode in os.video_modes() {
         let mut aspect_w = mode.width;
@@ -117,6 +102,10 @@ fn main<
         ));
     }
 
+    if modes.is_empty() {
+        return None;
+    }
+
     // Sort modes by pixel area, reversed
     modes.sort_by(|a, b| (b.0.width * b.0.height).cmp(&(a.0.width * a.0.height)));
 
@@ -227,6 +216,30 @@ fn main<
     os.set_text_highlight(false);
     println!();
 
+    mode_opt
+}
+
+fn main<
+    D: Disk,
+    M: Iterator<Item=OsMemoryEntry>,
+    V: Iterator<Item=OsVideoMode>
+>(os: &mut dyn Os<D, M, V>) -> (usize, KernelArgs) {
+    println!("Redox OS Bootloader {} on {}", env!("CARGO_PKG_VERSION"), os.name());
+
+    let mut fs = os.filesystem();
+
+    print!("RedoxFS ");
+    for i in 0..fs.header.1.uuid.len() {
+        if i == 4 || i == 6 || i == 8 || i == 10 {
+            print!("-");
+        }
+
+        print!("{:>02x}", fs.header.1.uuid[i]);
+    }
+    println!(": {} MiB", fs.header.1.size / MIBI as u64);
+
+    let mode_opt = select_mode(os);
+
     let stack_size = 128 * KIBI;
     let stack_base = os.alloc_zeroed_page_aligned(stack_size);
     if stack_base.is_null() {
diff --git a/src/os/uefi/arch/aarch64/mod.rs b/src/os/uefi/arch/aarch64/mod.rs
index fc888c67bb30366bd263c78acea3b2097ceb029a..9e2a251692bb54e618473613e5b2c07c7b14424f 100644
--- a/src/os/uefi/arch/aarch64/mod.rs
+++ b/src/os/uefi/arch/aarch64/mod.rs
@@ -1,16 +1,14 @@
-use alloc::{
-    string::String,
-};
-use core::{mem, ptr, slice};
-use std::fs::find;
-use std::proto::Protocol;
+use core::{mem, ptr};
 use uefi::guid::Guid;
-use uefi::memory::MemoryType;
 use uefi::status::{Error, Result};
 
+use crate::{
+    KernelArgs,
+    logger::LOGGER,
+};
+
 use super::super::{
-    disk::DiskEfi,
-    display::{Output},
+    OsEfi,
 };
 
 use self::memory_map::memory_map;
@@ -19,11 +17,7 @@ use self::paging::paging;
 mod memory_map;
 mod paging;
 
-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 PHYS_OFFSET: u64 = 0xFFFF800000000000;
 
 static mut DTB_PHYSICAL: u64 = 0;
 
@@ -32,22 +26,6 @@ pub extern "C" fn __chkstk() {
     //TODO
 }
 
-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();
@@ -55,13 +33,6 @@ unsafe fn exit_boot_services(key: usize) {
     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);
-}
-
 static DTB_GUID: Guid = Guid(0xb1b621d5, 0xf19c, 0x41a5, [0x83, 0x0b, 0xd9, 0x15, 0x2c, 0x69, 0xaa, 0xe0]);
 
 fn find_dtb() -> Result<()> {
@@ -79,143 +50,42 @@ fn find_dtb() -> Result<()> {
     Err(Error::NotFound)
 }
 
-fn redoxfs() -> Result<redoxfs::FileSystem<DiskEfi>> {
-    for (i, block_io) in DiskEfi::all().into_iter().enumerate() {
-        if !block_io.0.Media.LogicalPartition {
-            continue;
-        }
-
-        match redoxfs::FileSystem::open(block_io, Some(0)) {
-            Ok(ok) => return Ok(ok),
-            Err(err) => {
-                log::error!("Failed to open RedoxFS on block I/O {}: {}", i, err);
-            }
-        }
-    }
-    panic!("Failed to find RedoxFS");
+unsafe extern "C" fn kernel_entry(
+    page_phys: usize,
+    stack: u64,
+    func: u64,
+    args: *const KernelArgs,
+) -> ! {
+    // Read memory map and exit boot services
+    let key = memory_map();
+    exit_boot_services(key);
+
+    // Enable paging
+    asm!("msr daifset, #2");
+    paging();
+
+    // Call kernel entry
+    let entry_fn: extern "C" fn(dtb: u64) -> ! = mem::transmute(func);
+    entry_fn(DTB_PHYSICAL);
 }
 
-const MB: usize = 1024 * 1024;
+pub fn main() -> Result<()> {
+    LOGGER.init();
 
-fn inner() -> Result<()> {
     find_dtb()?;
 
-    //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 = if let Ok((_i, mut kernel_file)) = find("\\redox_bootloader\\kernel") {
-            let info = kernel_file.info()?;
-            let len = info.FileSize;
-
-            let kernel = unsafe {
-                let ptr = allocate_zero_pages((len as usize + page_size - 1) / page_size)?;
-                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 = kernel_file.read(&mut chunk)?;
-                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);
-
-            kernel
-        } else {
-            let mut fs = redoxfs()?;
+    let mut os = OsEfi {
+        st: std::system_table(),
+    };
 
-            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
-        };
-
-        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();
-    }
+    let (page_phys, args) = crate::main(&mut os);
 
     unsafe {
-        enter();
+        kernel_entry(
+            page_phys,
+            args.stack_base + args.stack_size + PHYS_OFFSET,
+            ptr::read((args.kernel_base + 0x18) as *const u64),
+            &args,
+        );
     }
 }
-
-pub fn main() -> Result<()> {
-    inner()?;
-
-    Ok(())
-}
diff --git a/src/os/uefi/arch/x86_64/memory_map.rs b/src/os/uefi/arch/x86_64/memory_map.rs
index 4e16fe15bfd0c03cfbc1eb5e7373120123a6a15c..3b6e18f0fdc2a4137d353f568318a1c1abada6a1 100644
--- a/src/os/uefi/arch/x86_64/memory_map.rs
+++ b/src/os/uefi/arch/x86_64/memory_map.rs
@@ -1,82 +1,6 @@
 use core::{mem, ptr};
-use log::error;
 use uefi::memory::{MemoryDescriptor, MemoryType};
 
-use crate::os::{OsMemoryEntry, OsMemoryKind};
-
-pub struct MemoryMapIter {
-    map: [u8; 4096],
-    map_size: usize,
-    descriptor_size: usize,
-    i: usize,
-}
-
-impl MemoryMapIter {
-    pub fn new() -> Self {
-        let uefi = std::system_table();
-
-        let mut map: [u8; 4096] = [0; 4096];
-        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
-        );
-
-        Self {
-            map,
-            map_size,
-            descriptor_size,
-            i: 0,
-        }
-    }
-}
-
-impl Iterator for MemoryMapIter {
-    type Item=OsMemoryEntry;
-    fn next(&mut self) -> Option<Self::Item> {
-        if
-            self.descriptor_size >= mem::size_of::<MemoryDescriptor>() &&
-            self.i < self.map_size/self.descriptor_size
-        {
-            let descriptor_ptr = unsafe { self.map.as_ptr().add(self.i * self.descriptor_size) };
-            self.i += 1;
-
-            let descriptor = unsafe { ptr::read(descriptor_ptr as *const MemoryDescriptor) };
-            let descriptor_type: MemoryType = unsafe { mem::transmute(descriptor.Type) };
-
-            Some(OsMemoryEntry {
-                base: descriptor.PhysicalStart.0,
-                //TODO: do not hard code page size
-                size: descriptor.NumberOfPages * 4096,
-                kind: match descriptor_type {
-                    MemoryType::EfiBootServicesCode |
-                    MemoryType::EfiBootServicesData |
-                    MemoryType::EfiConventionalMemory => {
-                        OsMemoryKind::Free
-                    },
-                    MemoryType::EfiLoaderCode |
-                    MemoryType::EfiLoaderData |
-                    MemoryType::EfiACPIReclaimMemory => {
-                        OsMemoryKind::Reclaim
-                    },
-                    _ => {
-                        OsMemoryKind::Reserved
-                    }
-                }
-            })
-        } else {
-            error!("Unknown memory descriptor size: {}", self.descriptor_size);
-            None
-        }
-    }
-}
-
 static MM_BASE: u64 = 0x500;
 static MM_SIZE: u64 = 0x4B00;
 
diff --git a/src/os/uefi/arch/x86_64/mod.rs b/src/os/uefi/arch/x86_64/mod.rs
index 3dab3c687e3c117c245dcd3aa72ebd03b28ef6a4..1eb2d0778be81c53fdd961ab9be59165d70215d7 100644
--- a/src/os/uefi/arch/x86_64/mod.rs
+++ b/src/os/uefi/arch/x86_64/mod.rs
@@ -1,32 +1,29 @@
-use core::{mem, ops::{ControlFlow, Try}, ptr, slice};
-use std::proto::Protocol;
-use std::vec::Vec;
-use uefi::status::{Result, Status};
-use uefi::guid::GuidKind;
-use uefi::memory::MemoryType;
-use uefi::system::SystemTable;
-use uefi::text::TextInputKey;
+use core::{
+    mem,
+    ptr
+};
+use std::{
+    vec::Vec,
+};
+use uefi::{
+    guid::GuidKind,
+    status::Result,
+};
 
 use crate::{
     KernelArgs,
-    Os,
-    OsKey,
-    OsVideoMode,
     logger::LOGGER,
 };
 
 use super::super::{
-    disk::DiskEfi,
-    display::{EdidActive, Output},
+    OsEfi,
 };
 
-use self::memory_map::{MemoryMapIter, memory_map};
+use self::memory_map::memory_map;
 use self::paging::paging_enter;
-use self::video_mode::VideoModeIter;
 
 mod memory_map;
 mod paging;
-mod video_mode;
 
 static PHYS_OFFSET: u64 = 0xFFFF800000000000;
 
@@ -119,173 +116,6 @@ fn find_acpi_table_pointers() {
     }
 }
 
-pub struct OsEfi {
-    st: &'static SystemTable,
-}
-
-fn status_to_result(status: Status) -> Result<usize> {
-    match status.branch() {
-        ControlFlow::Continue(ok) => Ok(ok),
-        ControlFlow::Break(err) => Err(err),
-    }
-}
-
-impl Os<
-    DiskEfi,
-    MemoryMapIter,
-    VideoModeIter
-> for OsEfi {
-    fn name(&self) -> &str {
-        "x86_64/UEFI"
-    }
-
-    fn alloc_zeroed_page_aligned(&self, size: usize) -> *mut u8 {
-        assert!(size != 0);
-
-        let page_size = self.page_size();
-        let pages = (size + page_size - 1) / page_size;
-
-        let ptr = {
-            let mut ptr = 0;
-            status_to_result(
-                (self.st.BootServices.AllocatePages)(
-                    0, // AllocateAnyPages
-                    MemoryType::EfiRuntimeServicesData, // Keeps this memory out of free space list
-                    pages,
-                    &mut ptr
-                )
-            ).unwrap();
-            ptr as *mut u8
-        };
-
-        assert!(!ptr.is_null());
-        unsafe { ptr::write_bytes(ptr, 0, pages * page_size) };
-        ptr
-    }
-
-    fn page_size(&self) -> usize {
-        4096
-    }
-
-    fn filesystem(&self) -> redoxfs::FileSystem<DiskEfi> {
-        for (i, block_io) in DiskEfi::all().into_iter().enumerate() {
-            if !block_io.0.Media.LogicalPartition {
-                continue;
-            }
-
-            match redoxfs::FileSystem::open(block_io, Some(0)) {
-                Ok(ok) => return ok,
-                Err(err) => match err.errno {
-                    // Ignore header not found error
-                    syscall::ENOENT => (),
-                    // Print any other errors
-                    _ => log::error!("Failed to open RedoxFS on block I/O {}: {}", i, err),
-                }
-            }
-        }
-        panic!("Failed to find RedoxFS");
-    }
-
-    fn memory(&self) -> MemoryMapIter {
-        MemoryMapIter::new()
-    }
-
-    fn video_modes(&self) -> VideoModeIter {
-        VideoModeIter::new()
-    }
-
-    fn set_video_mode(&self, mode: &mut OsVideoMode) {
-        let output = Output::one().unwrap();
-        status_to_result(
-            (output.0.SetMode)(output.0, mode.id)
-        ).unwrap();
-
-        // Update frame buffer base
-        mode.base = output.0.Mode.FrameBufferBase as u64;
-    }
-
-    fn best_resolution(&self) -> Option<(u32, u32)> {
-        //TODO: get this per output
-        match EdidActive::one() {
-            Ok(efi_edid) => {
-                let edid = unsafe {
-                    slice::from_raw_parts(efi_edid.0.Edid, efi_edid.0.SizeOfEdid as usize)
-                };
-
-                Some((
-                    (edid[0x38] as u32) | (((edid[0x3A] as u32) & 0xF0) << 4),
-                    (edid[0x3B] as u32) | (((edid[0x3D] as u32) & 0xF0) << 4),
-                ))
-            },
-            Err(err) => {
-                log::warn!("Failed to get EFI EDID: {:?}", err);
-
-                // Fallback to the current output resolution
-                match Output::one() {
-                    Ok(output) => {
-                        Some((
-                            output.0.Mode.Info.HorizontalResolution,
-                            output.0.Mode.Info.VerticalResolution,
-                        ))
-                    },
-                    Err(err) => {
-                        log::error!("Failed to get output: {:?}", err);
-                        None
-                    }
-                }
-            }
-        }
-    }
-
-    fn get_key(&self) -> OsKey {
-        //TODO: do not unwrap
-
-        let mut index = 0;
-        status_to_result(
-            (self.st.BootServices.WaitForEvent)(1, &self.st.ConsoleIn.WaitForKey, &mut index)
-        ).unwrap();
-
-        let mut key = TextInputKey {
-            ScanCode: 0,
-            UnicodeChar: 0
-        };
-        status_to_result(
-            (self.st.ConsoleIn.ReadKeyStroke)(self.st.ConsoleIn, &mut key)
-        ).unwrap();
-
-        match key.ScanCode {
-            0 => match key.UnicodeChar {
-                13 => OsKey::Enter,
-                _ => OsKey::Other,
-            },
-            1 => OsKey::Up,
-            2 => OsKey::Down,
-            3 => OsKey::Right,
-            4 => OsKey::Left,
-            _ => OsKey::Other,
-        }
-    }
-
-    fn get_text_position(&self) -> (usize, usize) {
-        (
-            self.st.ConsoleOut.Mode.CursorColumn as usize,
-            self.st.ConsoleOut.Mode.CursorRow as usize,
-        )
-    }
-
-    fn set_text_position(&self, x: usize, y: usize) {
-        status_to_result(
-            (self.st.ConsoleOut.SetCursorPosition)(self.st.ConsoleOut, x, y)
-        ).unwrap();
-    }
-
-    fn set_text_highlight(&self, highlight: bool) {
-        let attr = if highlight { 0x70 } else { 0x07 };
-        status_to_result(
-            (self.st.ConsoleOut.SetAttribute)(self.st.ConsoleOut, attr)
-        ).unwrap();
-    }
-}
 
 unsafe extern "C" fn kernel_entry(
     page_phys: usize,
@@ -314,12 +144,12 @@ unsafe extern "C" fn kernel_entry(
 pub fn main() -> Result<()> {
     LOGGER.init();
 
+    find_acpi_table_pointers();
+
     let mut os = OsEfi {
         st: std::system_table(),
     };
 
-    find_acpi_table_pointers();
-
     let (page_phys, mut args) = crate::main(&mut os);
 
     unsafe {
diff --git a/src/os/uefi/memory_map.rs b/src/os/uefi/memory_map.rs
new file mode 100644
index 0000000000000000000000000000000000000000..5ae3d5cdfc20d026d37500939b53e171ad09f446
--- /dev/null
+++ b/src/os/uefi/memory_map.rs
@@ -0,0 +1,78 @@
+use core::{mem, ptr};
+use log::error;
+use uefi::memory::{MemoryDescriptor, MemoryType};
+
+use crate::os::{OsMemoryEntry, OsMemoryKind};
+
+pub struct MemoryMapIter {
+    map: [u8; 4096],
+    map_size: usize,
+    descriptor_size: usize,
+    i: usize,
+}
+
+impl MemoryMapIter {
+    pub fn new() -> Self {
+        let uefi = std::system_table();
+
+        let mut map: [u8; 4096] = [0; 4096];
+        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
+        );
+
+        Self {
+            map,
+            map_size,
+            descriptor_size,
+            i: 0,
+        }
+    }
+}
+
+impl Iterator for MemoryMapIter {
+    type Item=OsMemoryEntry;
+    fn next(&mut self) -> Option<Self::Item> {
+        if
+            self.descriptor_size >= mem::size_of::<MemoryDescriptor>() &&
+            self.i < self.map_size/self.descriptor_size
+        {
+            let descriptor_ptr = unsafe { self.map.as_ptr().add(self.i * self.descriptor_size) };
+            self.i += 1;
+
+            let descriptor = unsafe { ptr::read(descriptor_ptr as *const MemoryDescriptor) };
+            let descriptor_type: MemoryType = unsafe { mem::transmute(descriptor.Type) };
+
+            Some(OsMemoryEntry {
+                base: descriptor.PhysicalStart.0,
+                //TODO: do not hard code page size
+                size: descriptor.NumberOfPages * 4096,
+                kind: match descriptor_type {
+                    MemoryType::EfiBootServicesCode |
+                    MemoryType::EfiBootServicesData |
+                    MemoryType::EfiConventionalMemory => {
+                        OsMemoryKind::Free
+                    },
+                    MemoryType::EfiLoaderCode |
+                    MemoryType::EfiLoaderData |
+                    MemoryType::EfiACPIReclaimMemory => {
+                        OsMemoryKind::Reclaim
+                    },
+                    _ => {
+                        OsMemoryKind::Reserved
+                    }
+                }
+            })
+        } else {
+            error!("Unknown memory descriptor size: {}", self.descriptor_size);
+            None
+        }
+    }
+}
diff --git a/src/os/uefi/mod.rs b/src/os/uefi/mod.rs
index c1c4f78ccb4b0434e53b1a07dfcd3e1fd734cd90..74c1f20fdf6da3f42039b73651dc04f2af42b8f9 100644
--- a/src/os/uefi/mod.rs
+++ b/src/os/uefi/mod.rs
@@ -1,11 +1,211 @@
-use core::ops::Try;
-use core::ptr;
-use uefi::reset::ResetType;
-use uefi::status::{Result, Status};
+use core::{
+    ops::{ControlFlow, Try},
+    ptr,
+    slice
+};
+use std::{
+    proto::Protocol,
+};
+use uefi::{
+    reset::ResetType,
+    memory::MemoryType,
+    status::{Result, Status},
+    system::SystemTable,
+    text::TextInputKey,
+};
+
+use crate::os::{
+    Os,
+    OsKey,
+    OsVideoMode,
+};
+
+use self::{
+    disk::DiskEfi,
+    display::{EdidActive, Output},
+    memory_map::MemoryMapIter,
+    video_mode::VideoModeIter,
+};
 
 mod arch;
 mod disk;
 mod display;
+mod memory_map;
+mod video_mode;
+
+fn status_to_result(status: Status) -> Result<usize> {
+    match status.branch() {
+        ControlFlow::Continue(ok) => Ok(ok),
+        ControlFlow::Break(err) => Err(err),
+    }
+}
+
+pub struct OsEfi {
+    st: &'static SystemTable,
+}
+
+impl Os<
+    DiskEfi,
+    MemoryMapIter,
+    VideoModeIter
+> for OsEfi {
+    #[cfg(target_arch = "aarch64")]
+    fn name(&self) -> &str {
+        "aarch64/UEFI"
+    }
+
+    #[cfg(target_arch = "x86_64")]
+    fn name(&self) -> &str {
+        "x86_64/UEFI"
+    }
+
+    fn alloc_zeroed_page_aligned(&self, size: usize) -> *mut u8 {
+        assert!(size != 0);
+
+        let page_size = self.page_size();
+        let pages = (size + page_size - 1) / page_size;
+
+        let ptr = {
+            let mut ptr = 0;
+            status_to_result(
+                (self.st.BootServices.AllocatePages)(
+                    0, // AllocateAnyPages
+                    MemoryType::EfiRuntimeServicesData, // Keeps this memory out of free space list
+                    pages,
+                    &mut ptr
+                )
+            ).unwrap();
+            ptr as *mut u8
+        };
+
+        assert!(!ptr.is_null());
+        unsafe { ptr::write_bytes(ptr, 0, pages * page_size) };
+        ptr
+    }
+
+    fn page_size(&self) -> usize {
+        4096
+    }
+
+    fn filesystem(&self) -> redoxfs::FileSystem<DiskEfi> {
+        for (i, block_io) in DiskEfi::all().into_iter().enumerate() {
+            if !block_io.0.Media.LogicalPartition {
+                continue;
+            }
+
+            match redoxfs::FileSystem::open(block_io, Some(0)) {
+                Ok(ok) => return ok,
+                Err(err) => match err.errno {
+                    // Ignore header not found error
+                    syscall::ENOENT => (),
+                    // Print any other errors
+                    _ => log::error!("Failed to open RedoxFS on block I/O {}: {}", i, err),
+                }
+            }
+        }
+        panic!("Failed to find RedoxFS");
+    }
+
+    fn memory(&self) -> MemoryMapIter {
+        MemoryMapIter::new()
+    }
+
+    fn video_modes(&self) -> VideoModeIter {
+        VideoModeIter::new()
+    }
+
+    fn set_video_mode(&self, mode: &mut OsVideoMode) {
+        let output = Output::one().unwrap();
+        status_to_result(
+            (output.0.SetMode)(output.0, mode.id)
+        ).unwrap();
+
+        // Update frame buffer base
+        mode.base = output.0.Mode.FrameBufferBase as u64;
+    }
+
+    fn best_resolution(&self) -> Option<(u32, u32)> {
+        //TODO: get this per output
+        match EdidActive::one() {
+            Ok(efi_edid) => {
+                let edid = unsafe {
+                    slice::from_raw_parts(efi_edid.0.Edid, efi_edid.0.SizeOfEdid as usize)
+                };
+
+                Some((
+                    (edid[0x38] as u32) | (((edid[0x3A] as u32) & 0xF0) << 4),
+                    (edid[0x3B] as u32) | (((edid[0x3D] as u32) & 0xF0) << 4),
+                ))
+            },
+            Err(err) => {
+                log::warn!("Failed to get EFI EDID: {:?}", err);
+
+                // Fallback to the current output resolution
+                match Output::one() {
+                    Ok(output) => {
+                        Some((
+                            output.0.Mode.Info.HorizontalResolution,
+                            output.0.Mode.Info.VerticalResolution,
+                        ))
+                    },
+                    Err(err) => {
+                        log::error!("Failed to get output: {:?}", err);
+                        None
+                    }
+                }
+            }
+        }
+    }
+
+    fn get_key(&self) -> OsKey {
+        //TODO: do not unwrap
+
+        let mut index = 0;
+        status_to_result(
+            (self.st.BootServices.WaitForEvent)(1, &self.st.ConsoleIn.WaitForKey, &mut index)
+        ).unwrap();
+
+        let mut key = TextInputKey {
+            ScanCode: 0,
+            UnicodeChar: 0
+        };
+        status_to_result(
+            (self.st.ConsoleIn.ReadKeyStroke)(self.st.ConsoleIn, &mut key)
+        ).unwrap();
+
+        match key.ScanCode {
+            0 => match key.UnicodeChar {
+                13 => OsKey::Enter,
+                _ => OsKey::Other,
+            },
+            1 => OsKey::Up,
+            2 => OsKey::Down,
+            3 => OsKey::Right,
+            4 => OsKey::Left,
+            _ => OsKey::Other,
+        }
+    }
+
+    fn get_text_position(&self) -> (usize, usize) {
+        (
+            self.st.ConsoleOut.Mode.CursorColumn as usize,
+            self.st.ConsoleOut.Mode.CursorRow as usize,
+        )
+    }
+
+    fn set_text_position(&self, x: usize, y: usize) {
+        status_to_result(
+            (self.st.ConsoleOut.SetCursorPosition)(self.st.ConsoleOut, x, y)
+        ).unwrap();
+    }
+
+    fn set_text_highlight(&self, highlight: bool) {
+        let attr = if highlight { 0x70 } else { 0x07 };
+        status_to_result(
+            (self.st.ConsoleOut.SetAttribute)(self.st.ConsoleOut, attr)
+        ).unwrap();
+    }
+}
 
 fn set_max_mode(output: &uefi::text::TextOutput) -> Result<()> {
     let mut max_i = None;
diff --git a/src/os/uefi/arch/x86_64/video_mode.rs b/src/os/uefi/video_mode.rs
similarity index 100%
rename from src/os/uefi/arch/x86_64/video_mode.rs
rename to src/os/uefi/video_mode.rs