diff --git a/bootloader/Cargo.lock b/bootloader/Cargo.lock new file mode 100644 index 0000000..cf9753c --- /dev/null +++ b/bootloader/Cargo.lock @@ -0,0 +1,264 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "aes" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e8b47f52ea9bae42228d07ec09eb676433d7c4ed1ebdf0f1d1c29ed446f1ab8" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", + "opaque-debug", +] + +[[package]] +name = "argon2" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db4ce4441f99dbd377ca8a8f57b698c44d0d6e712d8329b5040da5a64aa1ce73" +dependencies = [ + "base64ct", + "blake2", +] + +[[package]] +name = "autocfg" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" + +[[package]] +name = "base64ct" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" + +[[package]] +name = "blake2" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" +dependencies = [ + "digest", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "cipher" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ee52072ec15386f770805afd189a01c8841be8696bed250fa2f13c4c0d6dfb7" +dependencies = [ + "generic-array", +] + +[[package]] +name = "cpufeatures" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16b80225097f2e5ae4e7179dd2266824648f3e2f49d9134d584b76389d31c4c3" +dependencies = [ + "libc", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", + "subtle", +] + +[[package]] +name = "endian-num" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8f59926911ef34d1efb9ea1ee8ca78385df62ce700ccf2bcb149011bd226888" + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "libc" +version = "0.2.168" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aaeb2981e0606ca11d79718f8bb01164f1d6ed75080182d3abf017e6d244b6d" + +[[package]] +name = "linked_list_allocator" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9afa463f5405ee81cdb9cc2baf37e08ec7e4c8209442b5d72c04cfb2cd6e6286" +dependencies = [ + "spinning_top", +] + +[[package]] +name = "lock_api" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" + +[[package]] +name = "opaque-debug" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" + +[[package]] +name = "redox-path" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "436d45c2b6a5b159d43da708e62b25be3a4a3d5550d654b72216ade4c4bfd717" + +[[package]] +name = "redox_bootloader" +version = "1.0.0" +dependencies = [ + "bitflags 1.3.2", + "linked_list_allocator", + "log", + "redox_syscall", + "redoxfs", + "spin", +] + +[[package]] +name = "redox_syscall" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834" +dependencies = [ + "bitflags 2.6.0", +] + +[[package]] +name = "redoxfs" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c8756cd15a32143479f4a723ececcbe2b8e422242924b0628a163826aea8c4d" +dependencies = [ + "aes", + "argon2", + "base64ct", + "endian-num", + "libc", + "log", + "redox-path", + "redox_syscall", + "seahash", + "uuid", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "seahash" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +dependencies = [ + "lock_api", +] + +[[package]] +name = "spinning_top" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b9eb1a2f4c41445a3a0ff9abc5221c5fcd28e1f13cd7c0397706f9ac938ddb0" +dependencies = [ + "lock_api", +] + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + +[[package]] +name = "uuid" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8c5f0a0af699448548ad1a2fbf920fb4bee257eae39953ba95cb84891a0446a" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" diff --git a/bootloader/Cargo.toml b/bootloader/Cargo.toml new file mode 100644 index 0000000..5612e79 --- /dev/null +++ b/bootloader/Cargo.toml @@ -0,0 +1,97 @@ +# +# MARK: meta +# + +[package] +name = "redox_bootloader" +version = "1.0.0" +edition = "2021" +publish = false + +[lib] +name = "bootloader" +path = "src/main.rs" +crate-type = ["staticlib"] + + +# +# MARK: lints +# + + +[lints.rust] +unused_import_braces = "deny" +unit_bindings = "deny" +single_use_lifetimes = "deny" +non_ascii_idents = "deny" +macro_use_extern_crate = "deny" +elided_lifetimes_in_paths = "deny" +absolute_paths_not_starting_with_crate = "deny" +explicit_outlives_requirements = "warn" +unused_crate_dependencies = "warn" +#variant_size_differences = "warn" +redundant_lifetimes = "warn" +missing_docs = "allow" + +[lints.clippy] +needless_return = "allow" +new_without_default = "allow" +tabs_in_doc_comments = "allow" + +# Extra +expect_used = "deny" +#unwrap_used = "deny" +#panic = "deny" +dbg_macro = "deny" +allow_attributes = "deny" +create_dir = "deny" +filetype_is_file = "deny" +integer_division = "deny" +lossy_float_literal = "deny" +map_err_ignore = "deny" +mutex_atomic = "deny" +needless_raw_strings = "deny" +print_stderr = "deny" +print_stdout = "deny" +str_to_string = "deny" +string_add = "deny" +string_to_string = "deny" +unimplemented = "deny" +use_debug = "deny" +verbose_file_reads = "deny" +#wildcard_enum_match_arm = "deny" + +# Pedantic +large_types_passed_by_value = "deny" +match_on_vec_items = "deny" + +# Cargo +wildcard_dependencies = "deny" +negative_feature_names = "deny" +redundant_feature_names = "deny" +multiple_crate_versions = "deny" + + +# +# MARK: dependencies +# + + +[dependencies] +bitflags = "1.3.2" +linked_list_allocator = "0.10.5" +log = "0.4.17" +redox_syscall = "0.5" +spin = "0.9.5" + +[dependencies.redoxfs] +version = "0.6.0" +default-features = false +features = ["force-soft", "log"] + +# +# MARK: features +# + +[features] +serial_debug = [] diff --git a/bootloader/linkers/x86-unknown-none.ld b/bootloader/linkers/x86-unknown-none.ld new file mode 100644 index 0000000..21f5729 --- /dev/null +++ b/bootloader/linkers/x86-unknown-none.ld @@ -0,0 +1,57 @@ +ENTRY(start) +OUTPUT_FORMAT(elf32-i386) + +SECTIONS { + /* The start address must match bootloader.asm */ + . = 0x13000; + + . += SIZEOF_HEADERS; + . = ALIGN(4096); + + .text : { + __text_start = .; + *(.text*) + . = ALIGN(4096); + __text_end = .; + } + + .rodata : { + __rodata_start = .; + *(.rodata*) + . = ALIGN(4096); + __rodata_end = .; + } + + .data : { + __data_start = .; + *(.data*) + . = ALIGN(4096); + __data_end = .; + __bss_start = .; + *(.bss*) + . = ALIGN(4096); + __bss_end = .; + } + + .tdata : { + __tdata_start = .; + *(.tdata*) + . = ALIGN(4096); + __tdata_end = .; + __tbss_start = .; + *(.tbss*) + . += 8; + . = ALIGN(4096); + __tbss_end = .; + } + + __end = .; + + /DISCARD/ : { + *(.comment*) + *(.eh_frame*) + *(.gcc_except_table*) + *(.note*) + *(.rel.eh_frame*) + } +} diff --git a/bootloader/rust-toolchain.toml b/bootloader/rust-toolchain.toml new file mode 100644 index 0000000..46893dd --- /dev/null +++ b/bootloader/rust-toolchain.toml @@ -0,0 +1,3 @@ +[toolchain] +channel = "nightly-2024-05-11" +components = ["rust-src"] diff --git a/bootloader/src/arch/mod.rs b/bootloader/src/arch/mod.rs new file mode 100644 index 0000000..6f1d16f --- /dev/null +++ b/bootloader/src/arch/mod.rs @@ -0,0 +1,5 @@ +#[cfg(any(target_arch = "x86", target_arch = "x86_64"))] +pub use self::x86::*; + +#[cfg(any(target_arch = "x86", target_arch = "x86_64"))] +mod x86; diff --git a/bootloader/src/arch/x86/mod.rs b/bootloader/src/arch/x86/mod.rs new file mode 100644 index 0000000..5a705e6 --- /dev/null +++ b/bootloader/src/arch/x86/mod.rs @@ -0,0 +1,31 @@ +use redoxfs::Disk; + +use crate::os::{Os, OsVideoMode}; + +pub(crate) mod x32; +pub(crate) mod x64; + +pub unsafe fn paging_create>( + os: &dyn Os, + kernel_phys: u64, + kernel_size: u64, +) -> Option { + if crate::KERNEL_64BIT { + x64::paging_create(os, kernel_phys, kernel_size) + } else { + x32::paging_create(os, kernel_phys, kernel_size) + } +} + +pub unsafe fn paging_framebuffer>( + os: &dyn Os, + page_phys: usize, + framebuffer_phys: u64, + framebuffer_size: u64, +) -> Option { + if crate::KERNEL_64BIT { + x64::paging_framebuffer(os, page_phys, framebuffer_phys, framebuffer_size) + } else { + x32::paging_framebuffer(os, page_phys, framebuffer_phys, framebuffer_size) + } +} diff --git a/bootloader/src/arch/x86/x32.rs b/bootloader/src/arch/x86/x32.rs new file mode 100644 index 0000000..6be56a2 --- /dev/null +++ b/bootloader/src/arch/x86/x32.rs @@ -0,0 +1,89 @@ +use crate::area_add; +use crate::os::{Os, OsMemoryEntry, OsMemoryKind, OsVideoMode}; +use core::slice; +use redoxfs::Disk; + +const PAGE_ENTRIES: usize = 1024; +const PAGE_SIZE: usize = 4096; +pub(crate) const PHYS_OFFSET: u32 = 0x8000_0000; + +unsafe fn paging_allocate>( + os: &dyn Os, +) -> Option<&'static mut [u32]> { + let ptr = os.alloc_zeroed_page_aligned(PAGE_SIZE); + if !ptr.is_null() { + area_add(OsMemoryEntry { + base: ptr as u64, + size: PAGE_SIZE as u64, + kind: OsMemoryKind::Reclaim, + }); + Some(slice::from_raw_parts_mut(ptr as *mut u32, PAGE_ENTRIES)) + } else { + None + } +} + +pub unsafe fn paging_create>( + os: &dyn Os, + kernel_phys: u64, + kernel_size: u64, +) -> Option { + let pd = paging_allocate(os)?; + //Identity map 1 GiB using 4 MiB pages, also map at PHYS_OFFSET + for pd_i in 0..256 { + let addr = pd_i as u32 * 0x40_0000; + pd[pd_i] = addr | 1 << 7 | 1 << 1 | 1; + pd[pd_i + 512] = addr | 1 << 7 | 1 << 1 | 1; + } + + // Map kernel_size at kernel offset + let mut kernel_mapped = 0; + let mut pd_i = 0xC000_0000 / 0x40_0000; + while kernel_mapped < kernel_size && pd_i < pd.len() { + let pt = paging_allocate(os)?; + pd[pd_i] = pt.as_ptr() as u32 | 1 << 1 | 1; + pd_i += 1; + + let mut pt_i = 0; + while kernel_mapped < kernel_size && pt_i < pt.len() { + let addr = kernel_phys + kernel_mapped; + pt[pt_i] = addr as u32 | 1 << 1 | 1; + pt_i += 1; + kernel_mapped += PAGE_SIZE as u64; + } + } + assert!(kernel_mapped >= kernel_size); + + Some(pd.as_ptr() as usize) +} + +pub unsafe fn paging_framebuffer>( + os: &dyn Os, + page_phys: usize, + framebuffer_phys: u64, + framebuffer_size: u64, +) -> Option { + let framebuffer_virt = 0xD000_0000; // 256 MiB after kernel mapping, but before heap mapping + + let pd = slice::from_raw_parts_mut(page_phys as *mut u32, PAGE_ENTRIES); + + // Map framebuffer_size at framebuffer offset + let mut framebuffer_mapped = 0; + let mut pd_i = framebuffer_virt / 0x40_0000; + while framebuffer_mapped < framebuffer_size && pd_i < pd.len() { + let pt = paging_allocate(os)?; + pd[pd_i] = pt.as_ptr() as u32 | 1 << 1 | 1; + pd_i += 1; + + let mut pt_i = 0; + while framebuffer_mapped < framebuffer_size && pt_i < pt.len() { + let addr = framebuffer_phys + framebuffer_mapped; + pt[pt_i] = addr as u32 | 1 << 1 | 1; + pt_i += 1; + framebuffer_mapped += PAGE_SIZE as u64; + } + } + assert!(framebuffer_mapped >= framebuffer_size); + + Some(framebuffer_virt as u64) +} diff --git a/bootloader/src/arch/x86/x64.rs b/bootloader/src/arch/x86/x64.rs new file mode 100644 index 0000000..d37cccc --- /dev/null +++ b/bootloader/src/arch/x86/x64.rs @@ -0,0 +1,149 @@ +use core::slice; +use redoxfs::Disk; + +use crate::area_add; +use crate::os::{Os, OsMemoryEntry, OsMemoryKind, OsVideoMode}; + +const ENTRY_ADDRESS_MASK: u64 = 0x000F_FFFF_FFFF_F000; +const PAGE_ENTRIES: usize = 512; +const PAGE_SIZE: usize = 4096; +pub(crate) const PHYS_OFFSET: u64 = 0xFFFF_8000_0000_0000; + +unsafe fn paging_allocate>( + os: &dyn Os, +) -> Option<&'static mut [u64]> { + let ptr = os.alloc_zeroed_page_aligned(PAGE_SIZE); + if !ptr.is_null() { + area_add(OsMemoryEntry { + base: ptr as u64, + size: PAGE_SIZE as u64, + kind: OsMemoryKind::Reclaim, + }); + + Some(slice::from_raw_parts_mut(ptr as *mut u64, PAGE_ENTRIES)) + } else { + None + } +} + +const PRESENT: u64 = 1; +const WRITABLE: u64 = 1 << 1; +const LARGE: u64 = 1 << 7; + +pub unsafe fn paging_create>( + os: &dyn Os, + kernel_phys: u64, + kernel_size: u64, +) -> Option { + // Create PML4 + let pml4 = paging_allocate(os)?; + + { + // Create PDP for identity mapping + let pdp = paging_allocate(os)?; + + // Link first user and first kernel PML4 entry to PDP + pml4[0] = pdp.as_ptr() as u64 | WRITABLE | PRESENT; + pml4[256] = pdp.as_ptr() as u64 | WRITABLE | PRESENT; + + // Identity map 8 GiB using 2 MiB pages + for pdp_i in 0..8 { + let pd = paging_allocate(os)?; + pdp[pdp_i] = pd.as_ptr() as u64 | WRITABLE | PRESENT; + for pd_i in 0..pd.len() { + let addr = pdp_i as u64 * 0x4000_0000 + pd_i as u64 * 0x20_0000; + pd[pd_i] = addr | LARGE | WRITABLE | PRESENT; + } + } + } + + { + // Create PDP (spanning 512 GiB) for kernel mapping + let pdp = paging_allocate(os)?; + + // Link last PML4 entry to PDP + pml4[511] = pdp.as_ptr() as u64 | WRITABLE | PRESENT; + + // Create PD (spanning 1 GiB) for kernel mapping. + let pd = paging_allocate(os)?; + + // The kernel is mapped at -2^31, i.e. 0xFFFF_FFFF_8000_0000. Since a PD is 1 GiB, link + // the second last PDP entry to PD. + pdp[510] = pd.as_ptr() as u64 | WRITABLE | PRESENT; + + // Map kernel_size bytes to kernel offset, i.e. to the start of the PD. + + let mut kernel_mapped = 0; + + let mut pd_idx = 0; + while kernel_mapped < kernel_size && pd_idx < pd.len() { + let pt = paging_allocate(os)?; + pd[pd_idx] = pt.as_ptr() as u64 | WRITABLE | PRESENT; + pd_idx += 1; + + let mut pt_idx = 0; + while kernel_mapped < kernel_size && pt_idx < pt.len() { + let addr = kernel_phys + kernel_mapped; + pt[pt_idx] = addr | WRITABLE | PRESENT; + pt_idx += 1; + kernel_mapped += PAGE_SIZE as u64; + } + } + assert!(kernel_mapped >= kernel_size); + } + + Some(pml4.as_ptr() as usize) +} + +pub unsafe fn paging_framebuffer>( + os: &dyn Os, + page_phys: usize, + framebuffer_phys: u64, + framebuffer_size: u64, +) -> Option { + //TODO: smarter test for framebuffer already mapped + if framebuffer_phys + framebuffer_size <= 0x2_0000_0000 { + return Some(framebuffer_phys + PHYS_OFFSET); + } + + let pml4_i = ((framebuffer_phys / 0x80_0000_0000) + 256) as usize; + let mut pdp_i = ((framebuffer_phys % 0x80_0000_0000) / 0x4000_0000) as usize; + let mut pd_i = ((framebuffer_phys % 0x4000_0000) / 0x20_0000) as usize; + assert_eq!(framebuffer_phys % 0x20_0000, 0); + + let pml4 = slice::from_raw_parts_mut(page_phys as *mut u64, PAGE_ENTRIES); + + // Create PDP for framebuffer mapping + let pdp = if pml4[pml4_i] == 0 { + let pdp = paging_allocate(os)?; + pml4[pml4_i] = pdp.as_ptr() as u64 | 1 << 1 | 1; + pdp + } else { + slice::from_raw_parts_mut( + (pml4[pml4_i] & ENTRY_ADDRESS_MASK) as *mut u64, + PAGE_ENTRIES, + ) + }; + + // Map framebuffer_size at framebuffer offset + let mut framebuffer_mapped = 0; + while framebuffer_mapped < framebuffer_size && pdp_i < pdp.len() { + let pd = paging_allocate(os)?; + assert_eq!(pdp[pdp_i], 0); + pdp[pdp_i] = pd.as_ptr() as u64 | 1 << 1 | 1; + + while framebuffer_mapped < framebuffer_size && pd_i < pd.len() { + let addr = framebuffer_phys + framebuffer_mapped; + assert_eq!(pd[pd_i], 0); + pd[pd_i] = addr | 1 << 7 | 1 << 1 | 1; + framebuffer_mapped += 0x20_0000; + pd_i += 1; + } + + pdp_i += 1; + pd_i = 0; + } + assert!(framebuffer_mapped >= framebuffer_size); + + Some(framebuffer_phys + PHYS_OFFSET) +} diff --git a/bootloader/src/logger.rs b/bootloader/src/logger.rs new file mode 100644 index 0000000..09d210e --- /dev/null +++ b/bootloader/src/logger.rs @@ -0,0 +1,26 @@ +use log::{LevelFilter, Log, Metadata, Record}; + +pub static LOGGER: Logger = Logger; + +pub struct Logger; + +impl Logger { + pub fn init(&'static self) { + log::set_logger(self).unwrap(); + log::set_max_level(LevelFilter::Info); + } +} + +impl Log for Logger { + fn enabled(&self, _metadata: &Metadata<'_>) -> bool { + true + } + + fn log(&self, record: &Record<'_>) { + if self.enabled(record.metadata()) { + println!("{} - {}", record.level(), record.args()); + } + } + + fn flush(&self) {} +} diff --git a/bootloader/src/main.rs b/bootloader/src/main.rs new file mode 100644 index 0000000..b9c7529 --- /dev/null +++ b/bootloader/src/main.rs @@ -0,0 +1,642 @@ +#![no_std] +#![feature(alloc_error_handler)] +#![feature(int_roundings)] +#![feature(lang_items)] +#![allow(internal_features)] +#![feature(let_chains)] + +extern crate alloc; + +use alloc::{format, string::String, vec::Vec}; +use core::{ + cmp, + fmt::{self, Write}, + mem, ptr, slice, str, +}; +use redoxfs::Disk; + +use self::arch::{paging_create, paging_framebuffer}; +use self::os::{Os, OsHwDesc, OsKey, OsMemoryEntry, OsMemoryKind, OsVideoMode}; + +#[macro_use] +mod os; + +mod arch; +mod logger; +mod serial_16550; + +const KIBI: usize = 1024; +const MIBI: usize = KIBI * KIBI; + +//TODO: allocate this in a more reasonable manner +static mut AREAS: [OsMemoryEntry; 1024] = [OsMemoryEntry { + base: 0, + size: 0, + kind: OsMemoryKind::Null, +}; 1024]; +static mut AREAS_LEN: usize = 0; + +pub fn area_add(area: OsMemoryEntry) { + unsafe { + for existing_area in &mut AREAS[0..AREAS_LEN] { + if existing_area.kind == area.kind { + if existing_area.base.unchecked_add(existing_area.size) == area.base { + existing_area.size += area.size; + return; + } + if area.base.unchecked_add(area.size) == existing_area.base { + existing_area.base = area.base; + return; + } + } + } + *AREAS.get_mut(AREAS_LEN).expect("AREAS overflowed!") = area; + AREAS_LEN += 1; + } +} + +pub static mut KERNEL_64BIT: bool = false; + +pub static mut LIVE_OPT: Option<(u64, &'static [u8])> = None; + +struct SliceWriter<'a> { + slice: &'a mut [u8], + i: usize, +} + +impl Write for SliceWriter<'_> { + fn write_str(&mut self, s: &str) -> fmt::Result { + for b in s.bytes() { + if let Some(slice_b) = self.slice.get_mut(self.i) { + *slice_b = b; + self.i += 1; + } else { + return Err(fmt::Error); + } + } + Ok(()) + } +} + +#[allow(dead_code)] +#[derive(Debug)] +#[repr(C, packed(8))] +pub struct KernelArgs { + kernel_base: u64, + kernel_size: u64, + stack_base: u64, + stack_size: u64, + env_base: u64, + env_size: u64, + + /// The base pointer to the saved RSDP. + /// + /// This field can be NULL, and if so, the system has not booted with UEFI or in some other way + /// retrieved the RSDPs. The kernel or a userspace driver will thus try searching the BIOS + /// memory instead. On UEFI systems, searching is not guaranteed to actually work though. + acpi_rsdp_base: u64, + /// The size of the RSDP region. + acpi_rsdp_size: u64, + + areas_base: u64, + areas_size: u64, + + bootstrap_base: u64, + bootstrap_size: u64, +} + +fn select_mode>( + os: &dyn Os, + output_i: usize, +) -> Option { + let mut modes = Vec::new(); + for mode in os.video_modes(output_i) { + 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 + ), + )); + } + + 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))); + + // Set selected based on best resolution + print!("Output {}", output_i); + let mut selected = modes.get(0).map_or(0, |x| x.0.id); + if let Some((best_width, best_height)) = os.best_resolution(output_i) { + print!(", best resolution: {}x{}", best_width, best_height); + for (mode, _text) in modes.iter() { + if mode.width == best_width && mode.height == best_height { + selected = mode.id; + break; + } + } + } + println!(); + + println!("Arrow keys and enter select mode"); + println!(); + print!(" "); + + let (off_x, off_y) = os.get_text_position(); + let rows = 12; + let mut mode_opt = None; + 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 => { + if let Some(mode_i) = modes.iter().position(|x| x.0.id == selected) { + if let Some((mode, _text)) = modes.get(mode_i) { + mode_opt = Some(*mode); + } + } + break; + } + _ => (), + } + } + + os.set_text_position(0, off_y + rows); + os.set_text_highlight(false); + println!(); + + mode_opt +} + +fn redoxfs>( + os: &dyn Os, +) -> (redoxfs::FileSystem, Option<&'static [u8]>) { + let attempts = 10; + for attempt in 0..=attempts { + let mut password_opt = None; + if attempt > 0 { + print!("\rRedoxFS password ({}/{}): ", attempt, attempts); + + let mut password = String::new(); + + loop { + match os.get_key() { + OsKey::Backspace | OsKey::Delete => { + if !password.is_empty() { + print!("\x08 \x08"); + password.pop(); + } + } + OsKey::Char(c) => { + print!("*"); + password.push(c) + } + OsKey::Enter => break, + _ => (), + } + } + + // Erase password information + while os.get_text_position().0 > 0 { + print!("\x08 \x08"); + } + + if !password.is_empty() { + password_opt = Some(password); + } + } + match os.filesystem(password_opt.as_ref().map(|x| x.as_bytes())) { + Ok(fs) => { + return ( + fs, + password_opt.map(|password| { + // Copy password to page aligned memory + let password_size = password.len(); + let password_base = os.alloc_zeroed_page_aligned(password_size); + unsafe { + ptr::copy(password.as_ptr(), password_base, password_size); + slice::from_raw_parts(password_base, password_size) + } + }), + ); + } + Err(err) => match err.errno { + // Incorrect password, try again + syscall::ENOKEY => (), + _ => { + panic!("Failed to open RedoxFS: {}", err); + } + }, + } + } + panic!("RedoxFS out of unlock attempts"); +} + +#[derive(PartialEq)] +enum Filetype { + Elf, + Initfs, +} +fn load_to_memory( + os: &dyn Os>, + fs: &mut redoxfs::FileSystem, + dirname: &str, + filename: &str, + filetype: Filetype, +) -> &'static mut [u8] { + fs.tx(|tx| { + let dir_node = tx + .find_node(redoxfs::TreePtr::root(), dirname) + .unwrap_or_else(|err| panic!("Failed to find {} directory: {}", dirname, err)); + + let node = tx + .find_node(dir_node.ptr(), filename) + .unwrap_or_else(|err| panic!("Failed to find {} file: {}", filename, err)); + + let size = node.data().size(); + + print!("{}: 0/{} MiB", filename, size / MIBI as u64); + + let ptr = os.alloc_zeroed_page_aligned(size as usize); + if ptr.is_null() { + panic!("Failed to allocate memory for {}", filename); + } + + let slice = unsafe { slice::from_raw_parts_mut(ptr, size as usize) }; + + let mut i = 0; + for chunk in slice.chunks_mut(MIBI) { + print!( + "\r{}: {}/{} MiB", + filename, + i / MIBI as u64, + size / MIBI as u64 + ); + i += + tx.read_node_inner(&node, i, chunk) + .unwrap_or_else(|err| panic!("Failed to read `{}` file: {}", filename, err)) as u64; + } + println!( + "\r{}: {}/{} MiB", + filename, + i / MIBI as u64, + size / MIBI as u64 + ); + + if filetype == Filetype::Elf { + let magic = &slice[..4]; + if magic != b"\x7FELF" { + panic!("{} has invalid magic number {:#X?}", filename, magic); + } + } else if filetype == Filetype::Initfs { + let magic = &slice[..8]; + if magic != b"RedoxFtw" { + panic!("{} has invalid magic number {:#X?}", filename, magic); + } + } + + Ok(slice) + }) + .unwrap_or_else(|err| { + panic!( + "RedoxFS transaction failed while loading `{}`: {}", + filename, err + ) + }) +} + +fn elf_entry(data: &[u8]) -> (u64, bool) { + match (data[4], data[5]) { + // 32-bit, little endian + (1, 1) => ( + u32::from_le_bytes( + <[u8; 4]>::try_from(&data[0x18..0x18 + 4]).expect("conversion cannot fail"), + ) as u64, + false, + ), + // 32-bit, big endian + (1, 2) => ( + u32::from_be_bytes( + <[u8; 4]>::try_from(&data[0x18..0x18 + 4]).expect("conversion cannot fail"), + ) as u64, + false, + ), + // 64-bit, little endian + (2, 1) => ( + u64::from_le_bytes( + <[u8; 8]>::try_from(&data[0x18..0x18 + 8]).expect("conversion cannot fail"), + ), + true, + ), + // 64-bit, big endian + (2, 2) => ( + u64::from_be_bytes( + <[u8; 8]>::try_from(&data[0x18..0x18 + 8]).expect("conversion cannot fail"), + ), + true, + ), + (ei_class, ei_data) => { + panic!("Unsupported ELF EI_CLASS {} EI_DATA {}", ei_class, ei_data); + } + } +} + +fn main>(os: &dyn Os) -> (usize, u64, KernelArgs) { + println!( + "Redox OS Bootloader {} on {}", + env!("CARGO_PKG_VERSION"), + os.name() + ); + + let hwdesc = os.hwdesc(); + println!("Hardware descriptor: {:x?}", hwdesc); + let (acpi_rsdp_base, acpi_rsdp_size) = match hwdesc { + OsHwDesc::Acpi(base, size) => (base, size), + OsHwDesc::DeviceTree(base, size) => (base, size), + OsHwDesc::NotFound => (0, 0), + }; + + let (mut fs, password_opt) = redoxfs(os); + + print!("RedoxFS "); + for i in 0..fs.header.uuid().len() { + if i == 4 || i == 6 || i == 8 || i == 10 { + print!("-"); + } + + print!("{:>02x}", fs.header.uuid()[i]); + } + println!(": {} MiB", fs.header.size() / MIBI as u64); + println!(); + + let mut mode_opts = Vec::new(); + for output_i in 0..os.video_outputs() { + if output_i > 0 { + os.clear_text(); + } + mode_opts.push(select_mode(os, output_i)); + } + + let stack_size = 128 * KIBI; + let stack_base = os.alloc_zeroed_page_aligned(stack_size); + if stack_base.is_null() { + panic!("Failed to allocate memory for stack"); + } + + let live_opt = if cfg!(feature = "live") { + let size = fs.header.size(); + + print!("live: 0/{} MiB", size / MIBI as u64); + + let ptr = os.alloc_zeroed_page_aligned(size as usize); + if ptr.is_null() { + panic!("Failed to allocate memory for live"); + } + + let live = unsafe { slice::from_raw_parts_mut(ptr, size as usize) }; + + let mut i = 0; + for chunk in live.chunks_mut(MIBI) { + print!("\rlive: {}/{} MiB", i / MIBI as u64, size / MIBI as u64); + i += unsafe { + fs.disk + .read_at(fs.block + i / redoxfs::BLOCK_SIZE, chunk) + .expect("Failed to read live disk") as u64 + }; + } + println!("\rlive: {}/{} MiB", i / MIBI as u64, size / MIBI as u64); + + println!("Switching to live disk"); + unsafe { + LIVE_OPT = Some((fs.block, slice::from_raw_parts_mut(ptr, size as usize))); + } + + area_add(OsMemoryEntry { + base: live.as_ptr() as u64, + size: live.len() as u64, + kind: OsMemoryKind::Reserved, + }); + + Some(live) + } else { + None + }; + + let (kernel, kernel_entry) = { + let kernel = load_to_memory(os, &mut fs, "boot", "kernel", Filetype::Elf); + let (kernel_entry, kernel_64bit) = elf_entry(kernel); + unsafe { + KERNEL_64BIT = kernel_64bit; + } + (kernel, kernel_entry) + }; + + let (bootstrap_size, bootstrap_base) = { + let initfs_slice = load_to_memory(os, &mut fs, "boot", "initfs", Filetype::Initfs); + + let memory = unsafe { + let total_size = initfs_slice.len().next_multiple_of(4096); + let ptr = os.alloc_zeroed_page_aligned(total_size); + assert!(!ptr.is_null(), "failed to allocate bootstrap+initfs memory"); + core::slice::from_raw_parts_mut(ptr, total_size) + }; + memory[..initfs_slice.len()].copy_from_slice(initfs_slice); + + (memory.len() as u64, memory.as_mut_ptr() as u64) + }; + + let page_phys = unsafe { paging_create(os, kernel.as_ptr() as u64, kernel.len() as u64) } + .expect("Failed to set up paging"); + + let mut env_size = 64 * KIBI; + let env_base = os.alloc_zeroed_page_aligned(env_size); + if env_base.is_null() { + panic!("Failed to allocate memory for stack"); + } + + { + let mut w = SliceWriter { + slice: unsafe { slice::from_raw_parts_mut(env_base, env_size) }, + i: 0, + }; + + writeln!(w, "BOOT_MODE={}", os.name()).unwrap(); + + match hwdesc { + OsHwDesc::Acpi(addr, size) => { + writeln!(w, "RSDP_ADDR={:016x}", addr).unwrap(); + writeln!(w, "RSDP_SIZE={:016x}", size).unwrap(); + } + OsHwDesc::DeviceTree(addr, size) => { + writeln!(w, "DTB_ADDR={:016x}", addr).unwrap(); + writeln!(w, "DTB_SIZE={:016x}", size).unwrap(); + } + OsHwDesc::NotFound => {} + } + + if let Some(live) = live_opt { + writeln!(w, "DISK_LIVE_ADDR={:016x}", live.as_ptr() as usize).unwrap(); + writeln!(w, "DISK_LIVE_SIZE={:016x}", live.len()).unwrap(); + writeln!(w, "REDOXFS_BLOCK={:016x}", 0).unwrap(); + } else { + writeln!(w, "REDOXFS_BLOCK={:016x}", fs.block).unwrap(); + } + write!(w, "REDOXFS_UUID=").unwrap(); + for i in 0..fs.header.uuid().len() { + if i == 4 || i == 6 || i == 8 || i == 10 { + write!(w, "-").unwrap(); + } + + write!(w, "{:>02x}", fs.header.uuid()[i]).unwrap(); + } + writeln!(w).unwrap(); + if let Some(password) = password_opt { + writeln!( + w, + "REDOXFS_PASSWORD_ADDR={:016x}", + password.as_ptr() as usize + ) + .unwrap(); + writeln!(w, "REDOXFS_PASSWORD_SIZE={:016x}", password.len()).unwrap(); + } + + #[cfg(target_arch = "riscv64")] + { + let boot_hartid = os::efi_get_boot_hartid() + .expect("Could not retrieve boot hart id from EFI implementation!"); + writeln!(w, "BOOT_HART_ID={:016x}", boot_hartid).unwrap(); + } + + for output_i in 0..os.video_outputs() { + if let Some(mut mode) = mode_opts[output_i] { + // Set mode to get updated values + os.set_video_mode(output_i, &mut mode); + + if output_i == 0 { + let virt = unsafe { + paging_framebuffer( + os, + page_phys, + mode.base, + (mode.stride * mode.height * 4) as u64, + ) + } + .expect("Failed to map framebuffer"); + + writeln!(w, "FRAMEBUFFER_ADDR={:016x}", mode.base).unwrap(); + writeln!(w, "FRAMEBUFFER_VIRT={:016x}", virt).unwrap(); + writeln!(w, "FRAMEBUFFER_WIDTH={:016x}", mode.width).unwrap(); + writeln!(w, "FRAMEBUFFER_HEIGHT={:016x}", mode.height).unwrap(); + writeln!(w, "FRAMEBUFFER_STRIDE={:016x}", mode.stride).unwrap(); + } else { + writeln!( + w, + "FRAMEBUFFER{}={:#x},{},{},{}", + output_i, mode.base, mode.width, mode.height, mode.stride, + ) + .unwrap(); + } + } + } + + env_size = w.i; + } + + ( + page_phys, + kernel_entry, + KernelArgs { + kernel_base: kernel.as_ptr() as u64, + kernel_size: kernel.len() as u64, + stack_base: stack_base as u64, + stack_size: stack_size as u64, + env_base: env_base as u64, + env_size: env_size as u64, + acpi_rsdp_base, + acpi_rsdp_size, + areas_base: unsafe { AREAS.as_ptr() as u64 }, + areas_size: unsafe { (AREAS.len() * mem::size_of::()) as u64 }, + bootstrap_base, + bootstrap_size, + }, + ) +} diff --git a/bootloader/src/os/bios/disk.rs b/bootloader/src/os/bios/disk.rs new file mode 100644 index 0000000..a666d00 --- /dev/null +++ b/bootloader/src/os/bios/disk.rs @@ -0,0 +1,175 @@ +use core::{mem, ptr}; +use redoxfs::{Disk, BLOCK_SIZE}; +use syscall::error::{Error, Result, EIO}; + +use super::{ThunkData, DISK_ADDRESS_PACKET_ADDR, DISK_BIOS_ADDR}; + +const SECTOR_SIZE: u64 = 512; +const BLOCKS_PER_SECTOR: u64 = BLOCK_SIZE / SECTOR_SIZE; +// 128 sectors is the amount allocated for DISK_BIOS_ADDR +// 127 sectors is the maximum for many BIOSes +const MAX_SECTORS: u64 = 127; +const MAX_BLOCKS: u64 = MAX_SECTORS * SECTOR_SIZE / BLOCK_SIZE; + +#[allow(dead_code)] +#[derive(Clone, Copy)] +#[repr(C, packed)] +pub struct DiskAddressPacket { + size: u8, + reserved: u8, + sectors: u16, + buffer: u16, + segment: u16, + address: u64, +} + +impl DiskAddressPacket { + pub fn from_block(block: u64, count: u64) -> DiskAddressPacket { + let address = block * BLOCKS_PER_SECTOR; + let sectors = count * BLOCKS_PER_SECTOR; + assert!(sectors <= MAX_SECTORS); + DiskAddressPacket { + size: mem::size_of::() as u8, + reserved: 0, + sectors: sectors as u16, + buffer: (DISK_BIOS_ADDR & 0xF) as u16, + segment: (DISK_BIOS_ADDR >> 4) as u16, + address, + } + } +} + +pub struct DiskBios { + boot_disk: u8, + thunk13: extern "C" fn(), + chs_opt: Option<(u32, u32, u32)>, +} + +impl DiskBios { + pub fn new(boot_disk: u8, thunk13: extern "C" fn()) -> Self { + let chs_opt = unsafe { + let mut data = ThunkData::new(); + data.eax = 0x4100; + data.ebx = 0x55AA; + data.edx = boot_disk as u32; + + data.with(thunk13); + + if (data.ebx & 0xFFFF) == 0xAA55 { + // Extensions are installed, do not use CHS + None + } else { + // Extensions are not installed, get CHS geometry + data = ThunkData::new(); + data.eax = 0x0800; + data.edx = boot_disk as u32; + data.edi = 0; + + data.with(thunk13); + + //TODO: return result on error + let ah = ({ data.eax } >> 8) & 0xFF; + assert_eq!(ah, 0); + + let c = (data.ecx >> 8) & 0xFF | ((data.ecx >> 6) & 0x3) << 8; + let h = ((data.edx >> 8) & 0xFF) + 1; + let s = data.ecx & 0x3F; + + Some((c, h, s)) + } + }; + + Self { + boot_disk, + thunk13, + chs_opt, + } + } +} + +impl Disk for DiskBios { + unsafe fn read_at(&mut self, block: u64, buffer: &mut [u8]) -> Result { + // Optimization for live disks + if let Some(live) = crate::LIVE_OPT { + if block >= live.0 { + let start = ((block - live.0) * BLOCK_SIZE) as usize; + let end = start + buffer.len(); + if end <= live.1.len() { + buffer.copy_from_slice(&live.1[start..end]); + return Ok(buffer.len()); + } + } + } + + for (i, chunk) in buffer + .chunks_mut((MAX_BLOCKS * BLOCK_SIZE) as usize) + .enumerate() + { + let dap = DiskAddressPacket::from_block( + block + i as u64 * MAX_BLOCKS, + chunk.len() as u64 / BLOCK_SIZE, + ); + + if let Some((_, h_max, s_max)) = self.chs_opt { + let s = (dap.address % s_max as u64) + 1; + assert!(s <= 63, "invalid sector {}", s); + + let tmp = dap.address / s_max as u64; + let h = tmp % h_max as u64; + assert!(h <= 255, "invalid head {}", h); + + let c = tmp / h_max as u64; + assert!(c <= 1023, "invalid cylinder {}", c); + + let mut data = ThunkData::new(); + data.eax = 0x0200 | (dap.sectors as u32); + data.ebx = dap.buffer as u32; + data.ecx = + (s as u32) | (((c as u32) & 0xFF) << 8) | ((((c as u32) >> 8) & 0x3) << 6); + data.edx = (self.boot_disk as u32) | ((h as u32) << 8); + data.es = dap.segment; + + data.with(self.thunk13); + + //TODO: return result on error + let ah = ({ data.eax } >> 8) & 0xFF; + assert_eq!(ah, 0); + } else { + ptr::write(DISK_ADDRESS_PACKET_ADDR as *mut DiskAddressPacket, dap); + + let mut data = ThunkData::new(); + data.eax = 0x4200; + data.edx = self.boot_disk as u32; + data.esi = DISK_ADDRESS_PACKET_ADDR as u32; + + data.with(self.thunk13); + + //TODO: return result on error + let ah = ({ data.eax } >> 8) & 0xFF; + assert_eq!(ah, 0); + + //TODO: check blocks transferred + // dap = ptr::read(DISK_ADDRESS_PACKET_ADDR as *mut DiskAddressPacket); + } + + ptr::copy(DISK_BIOS_ADDR as *const u8, chunk.as_mut_ptr(), chunk.len()); + } + + Ok(buffer.len()) + } + + unsafe fn write_at(&mut self, block: u64, buffer: &[u8]) -> Result { + log::error!( + "DiskBios::write_at(0x{:X}, 0x{:X}:0x{:X}) not allowed", + block, + buffer.as_ptr() as usize, + buffer.len() + ); + Err(Error::new(EIO)) + } + + fn size(&mut self) -> Result { + log::error!("DiskBios::size not implemented"); + Err(Error::new(EIO)) + } +} diff --git a/bootloader/src/os/bios/macros.rs b/bootloader/src/os/bios/macros.rs new file mode 100644 index 0000000..38f4a31 --- /dev/null +++ b/bootloader/src/os/bios/macros.rs @@ -0,0 +1,20 @@ +/// Print to console +#[macro_export] +macro_rules! print { + ($($arg:tt)*) => ({ + use core::fmt::Write; + #[cfg(feature = "serial_debug")] + { + let _ = write!($crate::os::serial::COM1.lock(), $($arg)*); + } + let _ = write!($crate::os::VGA.lock(), $($arg)*); + }); +} + +/// Print with new line to console +#[macro_export] +macro_rules! println { + () => (print!("\n")); + ($fmt:expr) => (print!(concat!($fmt, "\n"))); + ($fmt:expr, $($arg:tt)*) => (print!(concat!($fmt, "\n"), $($arg)*)); +} diff --git a/bootloader/src/os/bios/memory_map.rs b/bootloader/src/os/bios/memory_map.rs new file mode 100644 index 0000000..a92cbae --- /dev/null +++ b/bootloader/src/os/bios/memory_map.rs @@ -0,0 +1,84 @@ +use core::{cmp, mem, ptr}; + +use crate::area_add; +use crate::os::{OsMemoryEntry, OsMemoryKind}; + +use super::{thunk::ThunkData, MEMORY_MAP_ADDR}; + +#[repr(C, packed)] +struct MemoryMapEntry { + pub base: 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 { + if self.first { + self.first = false; + } else if self.data.ebx == 0 { + return None; + } + + self.data.eax = 0xE820; + self.data.ecx = mem::size_of::() 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::() 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 { + 0 => OsMemoryKind::Null, + 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; + for entry in MemoryMapIter::new(thunk15) { + let heap_start = 1 * 1024 * 1024; + if { entry.kind } == OsMemoryKind::Free + && entry.base <= heap_start as u64 + && (entry.base + entry.size) >= heap_start as u64 + { + let heap_end = cmp::min(entry.base + entry.size, usize::MAX as u64) as usize; + if heap_end >= heap_start { + heap_limits = Some((heap_start, heap_end - heap_start)); + } + } + + area_add(entry); + } + heap_limits +} diff --git a/bootloader/src/os/bios/mod.rs b/bootloader/src/os/bios/mod.rs new file mode 100644 index 0000000..7e8c3d7 --- /dev/null +++ b/bootloader/src/os/bios/mod.rs @@ -0,0 +1,310 @@ +use alloc::alloc::{alloc_zeroed, Layout}; +use core::{convert::TryFrom, mem, ptr, slice}; +use linked_list_allocator::LockedHeap; +use spin::Mutex; + +use crate::logger::LOGGER; +use crate::os::{Os, OsHwDesc, OsKey, OsVideoMode}; +use crate::KernelArgs; + +use self::disk::DiskBios; +use self::memory_map::memory_map; +use self::thunk::ThunkData; +use self::vbe::VideoModeIter; +use self::vga::{Vga, VgaTextColor}; + +#[macro_use] +mod macros; + +mod disk; +mod memory_map; +mod panic; +pub(crate) mod serial; +mod thunk; +mod vbe; +mod vga; + +// Real mode memory allocation, for use with thunk +// 0x500 to 0x7BFF is free +const DISK_BIOS_ADDR: usize = 0x70000; // 64 KiB at 448 KiB, ends at 512 KiB +const VBE_CARD_INFO_ADDR: usize = 0x1000; // 512 bytes, ends at 0x11FF +const VBE_MODE_INFO_ADDR: usize = 0x1200; // 256 bytes, ends at 0x12FF +const VBE_EDID_ADDR: usize = 0x1300; // 128 bytes, ends at 0x137F +const MEMORY_MAP_ADDR: usize = 0x1380; // 24 bytes, ends at 0x1397 +const DISK_ADDRESS_PACKET_ADDR: usize = 0x1398; // 16 bytes, ends at 0x13A7 +const THUNK_STACK_ADDR: usize = 0x7C00; // Grows downwards +const VGA_ADDR: usize = 0xB8000; + +#[global_allocator] +static ALLOCATOR: LockedHeap = LockedHeap::empty(); + +pub(crate) static VGA: Mutex = Mutex::new(unsafe { Vga::new(VGA_ADDR, 80, 25) }); + +pub struct OsBios { + boot_disk: usize, + thunk10: extern "C" fn(), + thunk13: extern "C" fn(), + thunk15: extern "C" fn(), + thunk16: extern "C" fn(), +} + +#[allow(dead_code)] +#[derive(Copy, Clone, Debug)] +#[repr(C, packed)] +pub struct Rsdp { + signature: [u8; 8], + checksum: u8, + oemid: [u8; 6], + revision: u8, + rsdt_address: u32, +} + +#[allow(dead_code)] +#[derive(Copy, Clone, Debug)] +#[repr(C, packed)] +pub struct Xsdp { + rsdp: Rsdp, + + length: u32, + xsdt_address: u64, + extended_checksum: u8, + reserved: [u8; 3], +} + +unsafe fn search_rsdp(start: usize, end: usize) -> Option<(u64, u64)> { + // Align start up to 16 bytes + let mut addr = ((start + 15) / 16) * 16; + // Search until reading the end of the Rsdp would be past the end of the memory area + while addr + mem::size_of::() <= end { + let rsdp = ptr::read(addr as *const Rsdp); + if &rsdp.signature == b"RSD PTR " { + //TODO: check checksum? + if rsdp.revision == 0 { + return Some((addr as u64, mem::size_of::() as u64)); + } else if rsdp.revision == 2 { + let xsdp = ptr::read(addr as *const Xsdp); + //TODO: check extended checksum? + return Some((addr as u64, xsdp.length as u64)); + } + } + + // Rsdp is always aligned to 16 bytes + addr += 16; + } + None +} + +impl Os for OsBios { + fn name(&self) -> &str { + "x86/BIOS" + } + + 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 = + unsafe { alloc_zeroed(Layout::from_size_align(pages * page_size, page_size).unwrap()) }; + + assert!(!ptr.is_null()); + ptr + } + + fn page_size(&self) -> usize { + 4096 + } + + fn filesystem( + &self, + password_opt: Option<&[u8]>, + ) -> syscall::Result> { + let disk = DiskBios::new(u8::try_from(self.boot_disk).unwrap(), self.thunk13); + + //TODO: get block from partition table + let block = 2 * crate::MIBI as u64 / redoxfs::BLOCK_SIZE; + redoxfs::FileSystem::open(disk, password_opt, Some(block), false) + } + + fn hwdesc(&self) -> OsHwDesc { + // See ACPI specification - Finding the RSDP on IA-PC Systems + unsafe { + let ebda_segment = ptr::read(0x40E as *const u16); + let ebda_addr = (ebda_segment as usize) << 4; + if let Some((addr, size)) = + search_rsdp(ebda_addr, ebda_addr + 1024).or(search_rsdp(0xE0000, 0xFFFFF)) + { + // Copy to a page + let page_aligned = self.alloc_zeroed_page_aligned(size as usize); + ptr::copy(addr as *const u8, page_aligned, size as usize); + return OsHwDesc::Acpi(page_aligned as u64, size); + } + } + OsHwDesc::NotFound + } + + fn video_outputs(&self) -> usize { + //TODO: return 1 only if vbe supported? + 1 + } + + fn video_modes(&self, _output_i: usize) -> VideoModeIter { + VideoModeIter::new(self.thunk10) + } + + fn set_video_mode(&self, _output_i: usize, mode: &mut OsVideoMode) { + // Set video mode + let mut data = ThunkData::new(); + data.eax = 0x4F02; + data.ebx = mode.id; + unsafe { + data.with(self.thunk10); + } + //TODO: check result + } + + fn best_resolution(&self, _output_i: usize) -> Option<(u32, u32)> { + let mut data = ThunkData::new(); + data.eax = 0x4F15; + data.ebx = 0x01; + data.ecx = 0; + data.edx = 0; + data.edi = VBE_EDID_ADDR as u32; + unsafe { + data.with(self.thunk10); + } + + if data.eax == 0x4F { + let edid = unsafe { slice::from_raw_parts(VBE_EDID_ADDR as *const u8, 128) }; + + Some(( + (edid[0x38] as u32) | (((edid[0x3A] as u32) & 0xF0) << 4), + (edid[0x3B] as u32) | (((edid[0x3D] as u32) & 0xF0) << 4), + )) + } else { + log::warn!("Failed to get VBE EDID: 0x{:X}", { data.eax }); + None + } + } + + 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, + 0x0E => OsKey::Backspace, + 0x53 => OsKey::Delete, + 0x1C => OsKey::Enter, + _ => match data.eax as u8 { + 0 => OsKey::Other, + b => OsKey::Char(b as char), + }, + } + } + + fn clear_text(&self) { + //TODO: clear screen for VGA + } + + 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::Gray; + vga.fg = VgaTextColor::Black; + } else { + vga.bg = VgaTextColor::Black; + vga.fg = VgaTextColor::Gray; + } + } +} + +#[no_mangle] +pub unsafe extern "C" fn start( + kernel_entry: extern "C" fn( + page_table: usize, + stack: u64, + func: u64, + args: *const KernelArgs, + long_mode: usize, + ) -> !, + boot_disk: usize, + thunk10: extern "C" fn(), + thunk13: extern "C" fn(), + thunk15: extern "C" fn(), + thunk16: extern "C" fn(), +) -> ! { + #[cfg(feature = "serial_debug")] + { + let mut com1 = serial::COM1.lock(); + com1.init(); + com1.write(b"SERIAL\n"); + } + + { + // Make sure we are in mode 3 (80x25 text mode) + let mut data = ThunkData::new(); + data.eax = 0x03; + data.with(thunk10); + } + + { + // Disable cursor + let mut data = ThunkData::new(); + data.eax = 0x0100; + data.ecx = 0x3F00; + data.with(thunk10); + } + + // Clear screen + VGA.lock().clear(); + + // Set logger + LOGGER.init(); + + let mut os = OsBios { + boot_disk, + thunk10, + thunk13, + thunk15, + thunk16, + }; + + let (heap_start, heap_size) = memory_map(os.thunk15).expect("No memory for heap"); + + ALLOCATOR.lock().init(heap_start as *mut u8, heap_size); + + let (page_phys, func, args) = crate::main(&mut os); + + kernel_entry( + page_phys, + args.stack_base + + args.stack_size + + if crate::KERNEL_64BIT { + crate::arch::x64::PHYS_OFFSET as u64 + } else { + crate::arch::x32::PHYS_OFFSET as u64 + }, + func, + &args, + if crate::KERNEL_64BIT { 1 } else { 0 }, + ); +} diff --git a/bootloader/src/os/bios/panic.rs b/bootloader/src/os/bios/panic.rs new file mode 100644 index 0000000..552521c --- /dev/null +++ b/bootloader/src/os/bios/panic.rs @@ -0,0 +1,39 @@ +//! Intrinsics for panic handling + +use core::alloc::Layout; +use core::arch::asm; +use core::panic::PanicInfo; + +#[lang = "eh_personality"] +#[no_mangle] +pub extern "C" fn rust_eh_personality() {} + +/// Required to handle panics +#[panic_handler] +#[no_mangle] +pub fn rust_begin_unwind(info: &PanicInfo<'_>) -> ! { + unsafe { + println!("BOOTLOADER PANIC:\n{}", info); + loop { + asm!("hlt"); + } + } +} + +#[alloc_error_handler] +#[no_mangle] +#[allow(improper_ctypes_definitions)] // Layout is not repr(C) +pub extern "C" fn rust_oom(_layout: Layout) -> ! { + panic!("memory allocation failed"); +} + +#[allow(non_snake_case)] +#[no_mangle] +/// Required to handle panics +pub extern "C" fn _Unwind_Resume() -> ! { + loop { + unsafe { + asm!("hlt"); + } + } +} diff --git a/bootloader/src/os/bios/serial.rs b/bootloader/src/os/bios/serial.rs new file mode 100644 index 0000000..a673c1f --- /dev/null +++ b/bootloader/src/os/bios/serial.rs @@ -0,0 +1,9 @@ +use spin::Mutex; +use syscall::Pio; + +use crate::serial_16550::SerialPort; + +pub static COM1: Mutex>> = Mutex::new(SerialPort::>::new(0x3F8)); +pub static COM2: Mutex>> = Mutex::new(SerialPort::>::new(0x2F8)); +pub static COM3: Mutex>> = Mutex::new(SerialPort::>::new(0x3E8)); +pub static COM4: Mutex>> = Mutex::new(SerialPort::>::new(0x2E8)); diff --git a/bootloader/src/os/bios/thunk.rs b/bootloader/src/os/bios/thunk.rs new file mode 100644 index 0000000..036157b --- /dev/null +++ b/bootloader/src/os/bios/thunk.rs @@ -0,0 +1,46 @@ +use core::ptr; + +use super::THUNK_STACK_ADDR; + +#[allow(dead_code)] +#[derive(Clone, Copy, Debug)] +#[repr(C, packed)] +pub struct ThunkData { + pub es: u16, + pub edi: u32, + pub esi: u32, + pub ebp: u32, + pub ebx: u32, + pub edx: u32, + pub ecx: u32, + pub eax: u32, +} + +impl ThunkData { + pub fn new() -> Self { + Self { + es: 0, + edi: 0, + esi: 0, + ebp: 0, + ebx: 0, + edx: 0, + ecx: 0, + eax: 0, + } + } + + pub unsafe fn save(&self) { + ptr::write((THUNK_STACK_ADDR - 64) as *mut ThunkData, *self); + } + + pub unsafe fn load(&mut self) { + *self = ptr::read((THUNK_STACK_ADDR - 64) as *const ThunkData); + } + + pub unsafe fn with(&mut self, f: extern "C" fn()) { + self.save(); + f(); + self.load(); + } +} diff --git a/bootloader/src/os/bios/vbe.rs b/bootloader/src/os/bios/vbe.rs new file mode 100644 index 0000000..9498b8d --- /dev/null +++ b/bootloader/src/os/bios/vbe.rs @@ -0,0 +1,151 @@ +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(C, packed)] +pub struct VbeFarPtr { + pub offset: u16, + pub segment: u16, +} + +impl VbeFarPtr { + pub unsafe fn as_ptr(&self) -> *const T { + (((self.segment as usize) << 4) + (self.offset as usize)) as *const T + } +} + +#[derive(Clone, Copy, Debug)] +#[repr(C, packed)] +pub struct VbeCardInfo { + pub signature: [u8; 4], + pub version: u16, + pub oemstring: VbeFarPtr, + pub capabilities: [u8; 4], + pub videomodeptr: VbeFarPtr, + pub totalmemory: u16, + pub oemsoftwarerev: u16, + pub oemvendornameptr: VbeFarPtr, + pub oemproductnameptr: VbeFarPtr, + pub oemproductrevptr: VbeFarPtr, + pub reserved: [u8; 222], + pub oemdata: [u8; 256], +} + +#[derive(Clone, Copy, Debug)] +#[repr(C, 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 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) }; + unsafe { card_info.videomodeptr.as_ptr::() } + } 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 { + 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 stride that is not a multiple of 4 + let stride = mode_info.bytesperscanline as u32 / 4; + + return Some(OsVideoMode { + id: mode as u32, + width, + height, + stride, + base: mode_info.physbaseptr as u64, + }); + } else { + error!("Failed to read VBE mode 0x{:04X} info: 0x{:04X}", mode, { + data.eax + }); + } + } + } +} diff --git a/bootloader/src/os/bios/vga.rs b/bootloader/src/os/bios/vga.rs new file mode 100644 index 0000000..9bba2d8 --- /dev/null +++ b/bootloader/src/os/bios/vga.rs @@ -0,0 +1,119 @@ +use core::{fmt, slice}; + +#[derive(Clone, Copy)] +#[repr(C, packed)] +pub struct VgaTextBlock { + pub char: u8, + pub color: u8, +} + +#[allow(dead_code)] +#[derive(Clone, Copy)] +#[repr(u8)] +pub enum VgaTextColor { + Black = 0, + Blue = 1, + Green = 2, + Cyan = 3, + Red = 4, + Purple = 5, + Brown = 6, + Gray = 7, + DarkGray = 8, + LightBlue = 9, + LightGreen = 10, + LightCyan = 11, + LightRed = 12, + LightPurple = 13, + Yellow = 14, + White = 15, +} + +pub struct Vga { + pub base: usize, + pub width: usize, + pub height: usize, + pub x: usize, + pub y: usize, + pub bg: VgaTextColor, + pub fg: VgaTextColor, +} + +impl Vga { + pub const unsafe fn new(base: usize, width: usize, height: usize) -> Self { + Self { + base, + width, + height, + x: 0, + y: 0, + bg: VgaTextColor::Black, + fg: VgaTextColor::Gray, + } + } + + pub unsafe fn blocks(&mut self) -> &'static mut [VgaTextBlock] { + slice::from_raw_parts_mut(self.base as *mut VgaTextBlock, self.width * self.height) + } + + pub fn clear(&mut self) { + self.x = 0; + self.y = 0; + let blocks = unsafe { self.blocks() }; + for i in 0..blocks.len() { + blocks[i] = VgaTextBlock { + char: 0, + color: ((self.bg as u8) << 4) | (self.fg as u8), + }; + } + } +} + +impl fmt::Write for Vga { + fn write_str(&mut self, s: &str) -> Result<(), fmt::Error> { + let blocks = unsafe { self.blocks() }; + for c in s.chars() { + if self.x >= self.width { + self.x = 0; + self.y += 1; + } + while self.y >= self.height { + for y in 1..self.height { + for x in 0..self.width { + let i = y * self.width + x; + let j = i - self.width; + blocks[j] = blocks[i]; + if y + 1 == self.height { + blocks[i].char = 0; + } + } + } + self.y -= 1; + } + match c { + '\x08' => { + if self.x > 0 { + self.x -= 1; + } + } + '\r' => { + self.x = 0; + } + '\n' => { + self.x = 0; + self.y += 1; + } + _ => { + let i = self.y * self.width + self.x; + if let Some(block) = blocks.get_mut(i) { + block.char = c as u8; + block.color = ((self.bg as u8) << 4) | (self.fg as u8); + } + self.x += 1; + } + } + } + + Ok(()) + } +} diff --git a/bootloader/src/os/mod.rs b/bootloader/src/os/mod.rs new file mode 100644 index 0000000..e70e2f6 --- /dev/null +++ b/bootloader/src/os/mod.rs @@ -0,0 +1,81 @@ +use redoxfs::Disk; + +#[cfg(all(target_arch = "x86", target_os = "none"))] +pub use self::bios::*; + +#[cfg(all(target_arch = "x86", target_os = "none"))] +#[macro_use] +mod bios; + +#[derive(Clone, Copy, Debug)] +pub enum OsHwDesc { + Acpi(u64, u64), + DeviceTree(u64, u64), + NotFound, +} + +#[derive(Clone, Copy, Debug)] +pub enum OsKey { + Left, + Right, + Up, + Down, + Backspace, + Delete, + Enter, + Char(char), + Other, +} + +// Keep synced with BootloaderMemoryKind in kernel +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +#[repr(u64)] +pub enum OsMemoryKind { + Null = 0, + Free = 1, + Reclaim = 2, + Reserved = 3, +} + +// Keep synced with BootloaderMemoryEntry in kernel +#[derive(Clone, Copy, Debug)] +#[repr(C, packed(8))] +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 stride: u32, + pub base: u64, +} + +pub trait Os> { + fn name(&self) -> &str; + + fn alloc_zeroed_page_aligned(&self, size: usize) -> *mut u8; + + #[allow(dead_code)] + fn page_size(&self) -> usize; + + fn filesystem(&self, password_opt: Option<&[u8]>) -> syscall::Result>; + + fn hwdesc(&self) -> OsHwDesc; + + fn video_outputs(&self) -> usize; + fn video_modes(&self, output_i: usize) -> V; + fn set_video_mode(&self, output_i: usize, mode: &mut OsVideoMode); + fn best_resolution(&self, output_i: usize) -> Option<(u32, u32)>; + + fn get_key(&self) -> OsKey; + + fn clear_text(&self); + fn get_text_position(&self) -> (usize, usize); + fn set_text_position(&self, x: usize, y: usize); + fn set_text_highlight(&self, highlight: bool); +} diff --git a/bootloader/src/serial_16550.rs b/bootloader/src/serial_16550.rs new file mode 100644 index 0000000..5ea26c0 --- /dev/null +++ b/bootloader/src/serial_16550.rs @@ -0,0 +1,142 @@ +use bitflags::bitflags; +use core::convert::TryInto; +use core::fmt; +use core::ptr::{addr_of, addr_of_mut}; +#[cfg(any(target_arch = "x86", target_arch = "x86_64"))] +use syscall::io::Pio; +use syscall::io::{Io, Mmio, ReadOnly}; + +bitflags! { + /// Interrupt enable flags + struct IntEnFlags: u8 { + const RECEIVED = 1; + const SENT = 1 << 1; + const ERRORED = 1 << 2; + const STATUS_CHANGE = 1 << 3; + // 4 to 7 are unused + } +} + +bitflags! { + /// Line status flags + struct LineStsFlags: u8 { + const INPUT_FULL = 1; + // 1 to 4 unknown + const OUTPUT_EMPTY = 1 << 5; + // 6 and 7 unknown + } +} + +#[allow(dead_code)] +#[repr(C, packed)] +pub struct SerialPort { + /// Data register, read to receive, write to send + data: T, + /// Interrupt enable + int_en: T, + /// FIFO control + fifo_ctrl: T, + /// Line control + line_ctrl: T, + /// Modem control + modem_ctrl: T, + /// Line status + line_sts: ReadOnly, + /// Modem status + modem_sts: ReadOnly, +} + +#[cfg(any(target_arch = "x86", target_arch = "x86_64"))] +impl SerialPort> { + pub const fn new(base: u16) -> SerialPort> { + SerialPort { + data: Pio::new(base), + int_en: Pio::new(base + 1), + fifo_ctrl: Pio::new(base + 2), + line_ctrl: Pio::new(base + 3), + modem_ctrl: Pio::new(base + 4), + line_sts: ReadOnly::new(Pio::new(base + 5)), + modem_sts: ReadOnly::new(Pio::new(base + 6)), + } + } +} + +impl SerialPort> { + pub unsafe fn new(base: usize) -> &'static mut SerialPort> { + &mut *(base as *mut Self) + } +} + +impl SerialPort +where + T::Value: From + TryInto, +{ + pub fn init(&mut self) { + unsafe { + //TODO: Cleanup + // FIXME: Fix UB if unaligned + (&mut *addr_of_mut!(self.int_en)).write(0x00.into()); + (&mut *addr_of_mut!(self.line_ctrl)).write(0x80.into()); + (&mut *addr_of_mut!(self.data)).write(0x01.into()); + (&mut *addr_of_mut!(self.int_en)).write(0x00.into()); + (&mut *addr_of_mut!(self.line_ctrl)).write(0x03.into()); + (&mut *addr_of_mut!(self.fifo_ctrl)).write(0xC7.into()); + (&mut *addr_of_mut!(self.modem_ctrl)).write(0x0B.into()); + (&mut *addr_of_mut!(self.int_en)).write(0x01.into()); + } + } + + fn line_sts(&self) -> LineStsFlags { + LineStsFlags::from_bits_truncate( + (unsafe { &*addr_of!(self.line_sts) }.read() & 0xFF.into()) + .try_into() + .unwrap_or(0), + ) + } + + pub fn receive(&mut self) -> Option { + if self.line_sts().contains(LineStsFlags::INPUT_FULL) { + Some( + (unsafe { &*addr_of!(self.data) }.read() & 0xFF.into()) + .try_into() + .unwrap_or(0), + ) + } else { + None + } + } + + pub fn send(&mut self, data: u8) { + while !self.line_sts().contains(LineStsFlags::OUTPUT_EMPTY) {} + unsafe { &mut *addr_of_mut!(self.data) }.write(data.into()) + } + + pub fn write(&mut self, buf: &[u8]) { + for &b in buf { + match b { + 8 | 0x7F => { + self.send(8); + self.send(b' '); + self.send(8); + } + b'\n' => { + self.send(b'\r'); + self.send(b'\n'); + } + _ => { + self.send(b); + } + } + } + } +} + +impl fmt::Write for SerialPort +where + T::Value: From + TryInto, +{ + fn write_str(&mut self, s: &str) -> Result<(), fmt::Error> { + self.write(s.as_bytes()); + Ok(()) + } +} diff --git a/bootloader/targets/x86-unknown-none.json b/bootloader/targets/x86-unknown-none.json new file mode 100644 index 0000000..b3efe7b --- /dev/null +++ b/bootloader/targets/x86-unknown-none.json @@ -0,0 +1,28 @@ +{ + "llvm-target": "i686-unknown-none", + "target-endian": "little", + "target-pointer-width": "32", + "target-c-int-width": "32", + "data-layout": "e-m:e-p:32:32-p270:32:32-p271:32:32-p272:64:64-i128:128-f64:32:64-f80:32-n8:16:32-S128", + "arch": "x86", + "os": "none", + "env": "", + "vendor": "unknown", + "linker-flavor": "gcc", + "panic-strategy": "abort", + "pre-link-args": { + "gcc": ["-m32", "-nostdlib", "-static"] + }, + "features": "-mmx,-sse,-sse2,-sse3,-ssse3,-sse4.1,-sse4.2,-3dnow,-3dnowa,-avx,-avx2,+soft-float", + "dynamic-linking": false, + "executables": false, + "relocation-model": "static", + "code-model": "large", + "disable-redzone": true, + "frame-pointer": "always", + "exe-suffix": "", + "has-rpath": false, + "no-default-libraries": true, + "position-independent-executables": false, + "tls-model": "global-dynamic" +}