From aae67a40c17ff8c7c2ed0bd7eb590ca44b22c3ec Mon Sep 17 00:00:00 2001
From: Jeremy Soller <jackpot51@gmail.com>
Date: Tue, 8 Feb 2022 16:32:12 -0700
Subject: [PATCH] Move video mode selection to generic code

---
 linkers/x86-unknown-none.ld |   2 +-
 src/main.rs                 | 131 +++++++++++++++++
 src/os/bios/memory_map.rs   |  57 +++++++-
 src/os/bios/mod.rs          | 283 ++++++++++++------------------------
 src/os/bios/vbe.rs          | 173 ++++++++++++++++------
 src/os/mod.rs               |  53 +++++++
 6 files changed, 459 insertions(+), 240 deletions(-)

diff --git a/linkers/x86-unknown-none.ld b/linkers/x86-unknown-none.ld
index 7eb1b86..dc7e852 100644
--- a/linkers/x86-unknown-none.ld
+++ b/linkers/x86-unknown-none.ld
@@ -1,4 +1,4 @@
-ENTRY(kstart)
+ENTRY(start)
 OUTPUT_FORMAT(elf32-i386)
 
 SECTIONS {
diff --git a/src/main.rs b/src/main.rs
index 391c492..66ba720 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -16,8 +16,139 @@ extern crate alloc;
 #[macro_use]
 extern crate uefi_std as std;
 
+use alloc::vec::Vec;
+use core::cmp;
+use redoxfs::Disk;
+
+use self::os::{Os, OsKey, OsMemoryEntry, OsVideoMode};
+
 #[macro_use]
 mod os;
 
 mod arch;
 mod logger;
+
+fn main<
+    D: Disk,
+    M: Iterator<Item=OsMemoryEntry>,
+    V: Iterator<Item=OsVideoMode>
+>(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;
+        let mut aspect_h = mode.height;
+        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;
+            }
+        }
+
+        modes.push((
+            mode,
+            format!("{:>4}x{:<4} {:>3}:{:<3}", mode.width, mode.height, aspect_w, aspect_h)
+        ));
+    }
+
+    // Sort modes by pixel area, reversed
+    modes.sort_by(|a, b| (b.0.width * b.0.height).cmp(&(a.0.width * a.0.height)));
+
+    println!();
+    println!("Arrow keys and enter select mode");
+    println!();
+    print!(" ");
+
+    let (off_x, off_y) = os.get_text_position();
+    let rows = 12;
+    //TODO 0x4F03 VBE function to get current mode
+    let mut selected = modes.get(0).map_or(0, |x| x.0.id);
+    while ! modes.is_empty() {
+        let mut row = 0;
+        let mut col = 0;
+        for (mode, text) in modes.iter() {
+            if row >= rows {
+                col += 1;
+                row = 0;
+            }
+
+            os.set_text_position(off_x + col * 20, off_y + row);
+            os.set_text_highlight(mode.id == selected);
+
+            print!("{}", text);
+
+            row += 1;
+        }
+
+        // Read keypress
+        match os.get_key() {
+            OsKey::Left => {
+                if let Some(mut mode_i) = modes.iter().position(|x| x.0.id == 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.id;
+                    }
+                }
+            },
+            OsKey::Right => {
+                if let Some(mut mode_i) = modes.iter().position(|x| x.0.id == 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.id;
+                    }
+                }
+            },
+            OsKey::Up => {
+                if let Some(mut mode_i) = modes.iter().position(|x| x.0.id == 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.id;
+                    }
+                }
+            },
+            OsKey::Down => {
+                if let Some(mut mode_i) = modes.iter().position(|x| x.0.id == 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.id;
+                    }
+                }
+            },
+            OsKey::Enter => {
+                break;
+            },
+            _ => (),
+        }
+    }
+
+    os.set_text_position(0, off_y + rows);
+    os.set_text_highlight(false);
+    println!();
+
+    if let Some(mode_i) = modes.iter().position(|x| x.0.id == selected) {
+        if let Some((mode, _text)) = modes.get(mode_i) {
+            return Some(*mode);
+        }
+    }
+
+    None
+}
diff --git a/src/os/bios/memory_map.rs b/src/os/bios/memory_map.rs
index 1191464..02b565f 100644
--- a/src/os/bios/memory_map.rs
+++ b/src/os/bios/memory_map.rs
@@ -1,14 +1,65 @@
 use core::{cmp, mem, ptr};
 
+use crate::os::{OsMemoryEntry, OsMemoryKind};
+
 use super::{MEMORY_MAP_ADDR, thunk::ThunkData};
 
 #[repr(packed)]
 struct MemoryMapEntry {
     pub base: u64,
-    pub length: u64,
+    pub size: u64,
     pub kind: u32,
 }
 
+pub struct MemoryMapIter {
+    thunk15: extern "C" fn(),
+    data: ThunkData,
+    first: bool,
+}
+
+impl MemoryMapIter {
+    pub fn new(thunk15: extern "C" fn()) -> Self {
+        Self {
+            thunk15,
+            data: ThunkData::new(),
+            first: true,
+        }
+    }
+}
+
+impl Iterator for MemoryMapIter {
+    type Item=OsMemoryEntry;
+    fn next(&mut self) -> Option<Self::Item> {
+        if self.first {
+            self.first = false;
+        } else if self.data.ebx == 0 {
+            return None;
+        }
+
+        self.data.eax = 0xE820;
+        self.data.ecx = mem::size_of::<MemoryMapEntry>() as u32;
+        self.data.edx = 0x534D4150;
+        self.data.edi = MEMORY_MAP_ADDR as u32;
+
+        unsafe { self.data.with(self.thunk15); }
+
+        //TODO: return error?
+        assert_eq!({ self.data.eax }, 0x534D4150);
+        assert_eq!({ self.data.ecx }, mem::size_of::<MemoryMapEntry>() as u32);
+
+        let entry = unsafe { ptr::read(MEMORY_MAP_ADDR as *const MemoryMapEntry) };
+        Some(Self::Item {
+            base: entry.base,
+            size: entry.size,
+            kind: match entry.kind {
+                1 => OsMemoryKind::Free,
+                3 => OsMemoryKind::Reclaim,
+                _ => OsMemoryKind::Reserved,
+            },
+        })
+    }
+}
+
 pub unsafe fn memory_map(thunk15: extern "C" fn()) -> Option<(usize, usize)> {
     let mut heap_limits = None;
     let mut data = ThunkData::new();
@@ -29,10 +80,10 @@ pub unsafe fn memory_map(thunk15: extern "C" fn()) -> Option<(usize, usize)> {
         if
             entry.kind == 1 &&
             entry.base <= heap_start as u64 &&
-            (entry.base + entry.length) >= heap_start as u64
+            (entry.base + entry.size) >= heap_start as u64
         {
             let heap_end = cmp::min(
-                entry.base + entry.length,
+                entry.base + entry.size,
                 usize::MAX as u64
             ) as usize;
             if heap_end >= heap_start {
diff --git a/src/os/bios/mod.rs b/src/os/bios/mod.rs
index f6ddcf2..9db10f2 100644
--- a/src/os/bios/mod.rs
+++ b/src/os/bios/mod.rs
@@ -1,25 +1,22 @@
 use alloc::{
     string::String,
-    vec::Vec,
 };
 use core::{
     alloc::{GlobalAlloc, Layout},
-    cmp,
     convert::TryFrom,
-    ptr,
     slice,
 };
 use linked_list_allocator::LockedHeap;
-use log::error;
 use spin::Mutex;
 
-use crate::logger::LOGGER;
 use crate::arch::paging_create;
+use crate::logger::LOGGER;
+use crate::os::{Os, OsKey};
 
 use self::disk::DiskBios;
-use self::memory_map::memory_map;
+use self::memory_map::{memory_map, MemoryMapIter};
 use self::thunk::ThunkData;
-use self::vbe::{VbeCardInfo, VbeModeInfo};
+use self::vbe::VideoModeIter;
 use self::vga::{VgaTextColor, Vga};
 
 #[macro_use]
@@ -73,8 +70,80 @@ pub struct KernelArgs {
     acpi_rsdps_size: u64,
 }
 
+pub struct OsBios {
+    boot_disk: usize,
+    thunk10: extern "C" fn(),
+    thunk13: extern "C" fn(),
+    thunk15: extern "C" fn(),
+    thunk16: extern "C" fn(),
+}
+
+impl Os<
+    DiskBios,
+    MemoryMapIter,
+    VideoModeIter
+> for OsBios {
+    fn disk(&self) -> DiskBios {
+        DiskBios::new(u8::try_from(self.boot_disk).unwrap(), self.thunk13)
+    }
+
+    fn memory(&self) -> MemoryMapIter {
+        MemoryMapIter::new(self.thunk15)
+    }
+
+    fn video_modes(&self) -> VideoModeIter {
+        VideoModeIter::new(self.thunk10)
+    }
+
+    fn set_video_mode(&self, id: u32) {
+        // Set video mode
+        let mut data = ThunkData::new();
+        data.eax = 0x4F02;
+        data.ebx = id;
+        unsafe { data.with(self.thunk10); }
+        //TODO: check result
+    }
+
+    fn get_key(&self) -> OsKey {
+        // Read keypress
+        let mut data = ThunkData::new();
+        unsafe { data.with(self.thunk16); }
+        match (data.eax >> 8) as u8 {
+            0x4B => OsKey::Left,
+            0x4D => OsKey::Right,
+            0x48 => OsKey::Up,
+            0x50 => OsKey::Down,
+            0x1C => OsKey::Enter,
+            _ => OsKey::Other,
+        }
+    }
+
+    fn get_text_position(&self) -> (usize, usize) {
+        let vga = VGA.lock();
+        (vga.x, vga.y)
+    }
+
+    fn set_text_position(&self, x: usize, y: usize) {
+        //TODO: ensure this is inside bounds!
+        let mut vga = VGA.lock();
+        vga.x = x;
+        vga.y = y;
+    }
+
+    fn set_text_highlight(&self, highlight: bool) {
+        let mut vga = VGA.lock();
+        if highlight {
+            vga.bg = VgaTextColor::White;
+            vga.fg = VgaTextColor::Black;
+        } else {
+            vga.bg = VgaTextColor::DarkGray;
+            vga.fg = VgaTextColor::White;
+        }
+    }
+}
+
 #[no_mangle]
-pub unsafe extern "C" fn kstart(
+pub unsafe extern "C" fn start(
     kernel_entry: extern "C" fn(
         page_table: usize,
         stack: u64,
@@ -113,8 +182,16 @@ pub unsafe extern "C" fn kstart(
 
     ALLOCATOR.lock().init(heap_start, heap_size);
 
+    let mut os = OsBios {
+        boot_disk,
+        thunk10,
+        thunk13,
+        thunk15,
+        thunk16,
+    };
+
     // Locate kernel on RedoxFS
-    let disk = DiskBios::new(u8::try_from(boot_disk).unwrap(), thunk13);
+    let disk = os.disk();
 
     //TODO: get block from partition table
     let block = 1024 * 1024 / redoxfs::BLOCK_SIZE;
@@ -131,177 +208,7 @@ pub unsafe extern "C" fn kstart(
     }
     println!(": {} MiB", fs.header.1.size / 1024 / 1024);
 
-    let mut modes = Vec::new();
-    {
-        // Get card info
-        let mut data = ThunkData::new();
-        data.eax = 0x4F00;
-        data.edi = VBE_CARD_INFO_ADDR as u32;
-        data.with(thunk10);
-        if data.eax == 0x004F {
-            let card_info = ptr::read(VBE_CARD_INFO_ADDR as *const VbeCardInfo);
-
-            let mut mode_ptr = card_info.videomodeptr as *const u16;
-            loop {
-                // Ask for linear frame buffer with mode
-                let mode = *mode_ptr | (1 << 14);
-                if mode == 0xFFFF {
-                    break;
-                }
-                mode_ptr = mode_ptr.add(1);
-
-                // Get mode info
-                let mut data = ThunkData::new();
-                data.eax = 0x4F01;
-                data.ecx = mode as u32;
-                data.edi = VBE_MODE_INFO_ADDR as u32;
-                data.with(thunk10);
-                if data.eax == 0x004F {
-                    let mode_info = ptr::read(VBE_MODE_INFO_ADDR as *const VbeModeInfo);
-
-                    // We only support 32-bits per pixel modes
-                    if mode_info.bitsperpixel != 32 {
-                        continue;
-                    }
-
-                    let w = mode_info.xresolution as u32;
-                    let h = mode_info.yresolution as u32;
-
-                    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((
-                        mode,
-                        w, h,
-                        mode_info.physbaseptr,
-                        format!("{:>4}x{:<4} {:>3}:{:<3}", w, h, aspect_w, aspect_h)
-                    ));
-                } else {
-                    error!("Failed to read VBE mode 0x{:04X} info: 0x{:04X}", mode, { data.eax });
-                }
-            }
-        } else {
-            error!("Failed to read VBE card info: 0x{:04X}", { data.eax });
-        }
-    }
-
-    // Sort modes by pixel area, reversed
-    modes.sort_by(|a, b| (b.1 * b.2).cmp(&(a.1 * a.2)));
-
-    println!();
-    println!("Arrow keys and enter select mode");
-    println!();
-    print!(" ");
-
-    //TODO 0x4F03 VBE function to get current mode
-    let off_x = VGA.lock().x;
-    let off_y = VGA.lock().y;
-    let rows = 12;
-    let mut selected = modes.get(0).map_or(0, |x| x.0);
-    while ! modes.is_empty() {
-        let mut row = 0;
-        let mut col = 0;
-        for (mode, _w, _h, _ptr, text) in modes.iter() {
-            if row >= rows {
-                col += 1;
-                row = 0;
-            }
-
-            VGA.lock().x = off_x + col * 20;
-            VGA.lock().y = off_y + row;
-
-            if *mode == selected {
-                VGA.lock().bg = VgaTextColor::White;
-                VGA.lock().fg = VgaTextColor::Black;
-            } else {
-                VGA.lock().bg = VgaTextColor::DarkGray;
-                VGA.lock().fg = VgaTextColor::White;
-            }
-
-            print!("{}", text);
-
-            row += 1;
-        }
-
-        // Read keypress
-        let mut data = ThunkData::new();
-        data.with(thunk16);
-        match (data.eax >> 8) as u8 {
-            0x4B /* 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;
-                    }
-                }
-            },
-            0x4D /* 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;
-                    }
-                }
-            },
-            0x48 /* 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;
-                    }
-                }
-            },
-            0x50 /* 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;
-                    }
-                }
-            },
-            0x1C /* Enter */ => {
-                break;
-            },
-            _ => (),
-        }
-    }
-
-    VGA.lock().x = 0;
-    VGA.lock().y = off_y + rows;
-    VGA.lock().bg = VgaTextColor::DarkGray;
-    VGA.lock().fg = VgaTextColor::White;
-    println!();
+    let mode_opt = crate::main(&mut os);
 
     let kernel = {
         let node = fs.find_node("kernel", fs.header.1.root)
@@ -355,17 +262,11 @@ pub unsafe extern "C" fn kstart(
 
     let mut env = String::with_capacity(4096);
 
-    if let Some(mode_i) = modes.iter().position(|x| x.0 == selected) {
-        if let Some((mode, w, h, ptr, _text)) = modes.get(mode_i) {
-            let mut data = ThunkData::new();
-            data.eax = 0x4F02;
-            data.ebx = *mode as u32;
-            data.with(thunk10);
-
-            env.push_str(&format!("FRAMEBUFFER_ADDR={:016x}\n", ptr));
-            env.push_str(&format!("FRAMEBUFFER_WIDTH={:016x}\n", w));
-            env.push_str(&format!("FRAMEBUFFER_HEIGHT={:016x}\n", h));
-        }
+    if let Some(mode) = mode_opt {
+        env.push_str(&format!("FRAMEBUFFER_ADDR={:016x}\n", mode.base));
+        env.push_str(&format!("FRAMEBUFFER_WIDTH={:016x}\n", mode.width));
+        env.push_str(&format!("FRAMEBUFFER_HEIGHT={:016x}\n", mode.height));
+        os.set_video_mode(mode.id);
     }
     env.push_str(&format!("REDOXFS_BLOCK={:016x}\n", fs.block));
     env.push_str("REDOXFS_UUID=");
diff --git a/src/os/bios/vbe.rs b/src/os/bios/vbe.rs
index f396afd..57e2805 100644
--- a/src/os/bios/vbe.rs
+++ b/src/os/bios/vbe.rs
@@ -1,54 +1,137 @@
+use core::ptr;
+use log::error;
+
+use crate::os::OsVideoMode;
+
+use super::{ThunkData, VBE_CARD_INFO_ADDR, VBE_MODE_INFO_ADDR};
+
 #[derive(Clone, Copy, Debug)]
 #[repr(packed)]
 pub struct VbeCardInfo {
-	pub signature: [u8; 4],
-	pub version: u16,
-	pub oemstring: u32,
-	pub capabilities: u32,
-	pub videomodeptr: u32,
-	pub totalmemory: u16,
-	pub oemsoftwarerev: u16,
-	pub oemvendornameptr: u32,
-	pub oemproductnameptr: u32,
-	pub oemproductrevptr: u32,
-	pub reserved: [u8; 222],
-	pub oemdata: [u8; 256],
+    pub signature: [u8; 4],
+    pub version: u16,
+    pub oemstring: u32,
+    pub capabilities: u32,
+    pub videomodeptr: u32,
+    pub totalmemory: u16,
+    pub oemsoftwarerev: u16,
+    pub oemvendornameptr: u32,
+    pub oemproductnameptr: u32,
+    pub oemproductrevptr: u32,
+    pub reserved: [u8; 222],
+    pub oemdata: [u8; 256],
 }
 
 #[derive(Clone, Copy, Debug)]
 #[repr(packed)]
 pub struct VbeModeInfo {
-	pub attributes: u16,
-	pub win_a: u8,
-	pub win_b: u8,
-	pub granularity: u16,
-	pub winsize: u16,
-	pub segment_a: u16,
-	pub segment_b: u16,
-	pub winfuncptr: u32,
-	pub bytesperscanline: u16,
-	pub xresolution: u16,
-	pub yresolution: u16,
-	pub xcharsize: u8,
-	pub ycharsize: u8,
-	pub numberofplanes: u8,
-	pub bitsperpixel: u8,
-	pub numberofbanks: u8,
-	pub memorymodel: u8,
-	pub banksize: u8,
-	pub numberofimagepages: u8,
-	pub unused: u8,
-	pub redmasksize: u8,
-	pub redfieldposition: u8,
-	pub greenmasksize: u8,
-	pub greenfieldposition: u8,
-	pub bluemasksize: u8,
-	pub bluefieldposition: u8,
-	pub rsvdmasksize: u8,
-	pub rsvdfieldposition: u8,
-	pub directcolormodeinfo: u8,
-	pub physbaseptr: u32,
-	pub offscreenmemoryoffset: u32,
-	pub offscreenmemsize: u16,
-	pub reserved: [u8; 206],
+    pub attributes: u16,
+    pub win_a: u8,
+    pub win_b: u8,
+    pub granularity: u16,
+    pub winsize: u16,
+    pub segment_a: u16,
+    pub segment_b: u16,
+    pub winfuncptr: u32,
+    pub bytesperscanline: u16,
+    pub xresolution: u16,
+    pub yresolution: u16,
+    pub xcharsize: u8,
+    pub ycharsize: u8,
+    pub numberofplanes: u8,
+    pub bitsperpixel: u8,
+    pub numberofbanks: u8,
+    pub memorymodel: u8,
+    pub banksize: u8,
+    pub numberofimagepages: u8,
+    pub unused: u8,
+    pub redmasksize: u8,
+    pub redfieldposition: u8,
+    pub greenmasksize: u8,
+    pub greenfieldposition: u8,
+    pub bluemasksize: u8,
+    pub bluefieldposition: u8,
+    pub rsvdmasksize: u8,
+    pub rsvdfieldposition: u8,
+    pub directcolormodeinfo: u8,
+    pub physbaseptr: u32,
+    pub offscreenmemoryoffset: u32,
+    pub offscreenmemsize: u16,
+    pub reserved: [u8; 206],
+}
+
+pub struct VideoModeIter {
+    thunk10: extern "C" fn(),
+    mode_ptr: *const u16,
+}
+
+impl VideoModeIter {
+    pub fn new(thunk10: extern "C" fn()) -> Self {
+        // Get card info
+        let mut data = ThunkData::new();
+        data.eax = 0x4F00;
+        data.edi = VBE_CARD_INFO_ADDR as u32;
+        unsafe { data.with(thunk10); }
+        let mode_ptr = if data.eax == 0x004F {
+            let card_info = unsafe { ptr::read(VBE_CARD_INFO_ADDR as *const VbeCardInfo) };
+            card_info.videomodeptr as *const u16
+        } else {
+            error!("Failed to read VBE card info: 0x{:04X}", { data.eax });
+            ptr::null()
+        };
+        Self {
+            thunk10,
+            mode_ptr
+        }
+    }
+}
+
+impl Iterator for VideoModeIter {
+    type Item = OsVideoMode;
+    fn next(&mut self) -> Option<Self::Item> {
+        if self.mode_ptr.is_null() {
+            return None;
+        }
+
+        loop {
+            // Set bit 14 to get linear frame buffer
+            let mode = unsafe { *self.mode_ptr } | (1 << 14);
+            if mode == 0xFFFF {
+                return None;
+            }
+            self.mode_ptr = unsafe { self.mode_ptr.add(1) };
+
+            // Get mode info
+            let mut data = ThunkData::new();
+            data.eax = 0x4F01;
+            data.ecx = mode as u32;
+            data.edi = VBE_MODE_INFO_ADDR as u32;
+            unsafe { data.with(self.thunk10); }
+            if data.eax == 0x004F {
+                let mode_info = unsafe { ptr::read(VBE_MODE_INFO_ADDR as *const VbeModeInfo) };
+
+                // We only support 32-bits per pixel modes
+                if mode_info.bitsperpixel != 32 {
+                    continue;
+                }
+
+                let width = mode_info.xresolution as u32;
+                let height = mode_info.yresolution as u32;
+
+                //TODO: support resolutions that are not perfect multiples of 4
+                if width % 4 != 0 {
+                    continue;
+                }
+
+                return Some(OsVideoMode {
+                    id: mode as u32,
+                    width,
+                    height,
+                    base: mode_info.physbaseptr as u64,
+                });
+            } else {
+                error!("Failed to read VBE mode 0x{:04X} info: 0x{:04X}", mode, { data.eax });
+            }
+        }
+    }
 }
diff --git a/src/os/mod.rs b/src/os/mod.rs
index 10f3e58..0e6ab87 100644
--- a/src/os/mod.rs
+++ b/src/os/mod.rs
@@ -1,3 +1,5 @@
+use redoxfs::Disk;
+
 #[cfg(all(target_arch = "x86", target_os = "none"))]
 pub use self::bios::*;
 
@@ -11,3 +13,54 @@ pub use self::uefi::*;
 #[cfg(target_os = "uefi")]
 #[macro_use]
 mod uefi;
+
+#[derive(Clone, Copy, Debug)]
+pub enum OsKey {
+    Left,
+    Right,
+    Up,
+    Down,
+    Enter,
+    Other,
+}
+
+#[derive(Clone, Copy, Debug)]
+pub enum OsMemoryKind {
+    Free,
+    Reclaim,
+    Reserved,
+}
+
+#[derive(Clone, Copy, Debug)]
+pub struct OsMemoryEntry {
+    pub base: u64,
+    pub size: u64,
+    pub kind: OsMemoryKind,
+}
+
+#[derive(Clone, Copy, Debug)]
+pub struct OsVideoMode {
+    pub id: u32,
+    pub width: u32,
+    pub height: u32,
+    pub base: u64,
+}
+
+pub trait Os<
+    D: Disk,
+    M: Iterator<Item=OsMemoryEntry>,
+    V: Iterator<Item=OsVideoMode>
+> {
+    fn disk(&self) -> D;
+
+    fn memory(&self) -> M;
+
+    fn video_modes(&self) -> V;
+    fn set_video_mode(&self, id: u32);
+
+    fn get_key(&self) -> OsKey;
+
+    fn get_text_position(&self) -> (usize, usize);
+    fn set_text_position(&self, x: usize, y: usize);
+    fn set_text_highlight(&self, highlight: bool);
+}
-- 
GitLab