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